跳至主要内容

PowerShell: How to Zip (and Unzip) Files

專案的佈署腳本中, 通常會存在複製程式碼到特定資料夾的步驟. 這次遇到的問題是複製中文檔名時發生錯誤.

88Copy-Item : eLkƻs{ءC
89 u:1 r:1
90+ Copy-Item -Path F:_emp\BIN\* -Destination F:\xxx\xxx ...
91+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
92 + CategoryInfo : InvalidArgument: (F:_emp\BIN\_theme:String) [Copy-Item]APSArgumentExceptio
93 n
94 + FullyQualifiedErrorId : CopyContainerItemToLeafError,Microsoft.PowerShell.Commands.CopyItemCommand

然而同一份 gitlab.yml 檔, 有時成功有時失敗. 也許和不同 runner 有關, 加上發案方的許多防火牆限制, 都讓追查問題相當困難.

實話說 Windows 的編碼問題, 對於長期習慣在 UNiX (macOS, Linux) 下工作人來說, 有些一頭霧水. 雖然這次的情況, 是個契機去好好了解與學習 Windows 與 PowerShell 下如何處理與避免編碼問題. 可惜恰好遇上繁忙, 無法慢慢去釐清與學習了解.

Workaround 的解法是, 在 CI/CD Pipeline 編譯成功之後, 把檔案壓縮打包. 後續再解壓縮來佈署.

在 PowerShell 5.0 以後可透過 Compress-Archive 和 Expand-Archive 來壓縮和解壓縮 Zip 檔案. 常用簡單命令如後:

建立 Zip 壓縮檔

# 建立 Zip 壓縮檔
Compress-Archive -Path D:\MyFolder -DestinationPath D:\MyArchive.zip

這樣 PowerShell 就會將 D:\MyFolder 這整個目錄壓縮成 D:\MyArchive.zip 這個壓縮檔.
如果要分開指定多個檔案, 可以用逗號分隔:

# 壓縮多個檔案
Compress-Archive -Path D:\MyFolder\MyFile.docx, D:\MyFolder\MyImg.jpg -DestinationPath D:\MyArchive.zip

強制覆蓋舊檔

在建立壓縮檔時, 目標的 Zip 壓縮檔已經存在的話, Compress-Archive 就會顯示錯誤訊息. 加上 -Force 可以強制覆蓋舊的 Zip 壓縮檔.

# 強制建立 Zip 壓縮檔
Compress-Archive -Path D:\MyFolder -Force -DestinationPath D:\MyArchive.zip

更新壓縮檔

和強制覆蓋不同, 若加上 -Update 參數則可更新原有壓縮檔的內容. PowerShell 會比對 D:\MyArchive.zip 與 D:\MyFolder\*.txt 中的檔案, 將有更新的檔案加入 D:\MyArchive.zip 壓縮檔中, 若有新增的檔案也會一併加入.

# 更新壓縮檔
Compress-Archive -Path D:\MyFolder\*.txt -Update -DestinationPath D:\MyArchive.zip

壓縮層級

壓縮層級會影響壓縮所耗費的時間以及所產生的壓縮檔大小. 可以使用 -CompressionLevel 來指定壓縮層級. 可用的選項如下:

層級說明
NoCompression不要進行壓縮. 速度最快, 檔案最大.
Fastest使用快速的方式壓縮, 會產生較大的壓縮檔.
Optimal最佳壓縮, 產生的壓縮檔較小. 為預設模式.

解壓縮 Zip 檔

使用 Expand-Archive 指令:

# 解壓縮 Zip 檔
Expand-Archive -Path D:\MyArchive.Zip -DestinationPath D:\Output

執行之後就會將 D:\MyArchive.Zip 解壓縮至 D:\Output 目錄中. 如果 D:\Output 目錄不存在的話, 解壓縮時它會自動建立.

後記更新​

最後發現, 和中文檔名一點關係也沒.

簡單來說, 在 PowerShell 下使用 Copy-Item 時, 最好是搭配 -LiteralPath 而非 -Path 以避免錯誤(詳見 PowerShell Path vs LiteralPath). 至於原因, 暫時無力追蹤理解. 請先參考 Copy-Item 官方說明 自行求解.

Reference​