PowerShell Version 2.0 でのファイル圧縮 (ZIP 化)

2280
NO IMAGE

PowerShell でファイルの圧縮を行う必要があったが PowerShell の version によって想定したコマンドレットが使用できず、代替え手段を検討した際の備忘録。

作業した環境

開発環境

  • windows10
    • PowerShell Version 5.1.18362.145
    • .NET Framework 4.8

運用環境

  • Windows Server 2008 R2
    • PowerShell Version 2.0
    • .NET Framework 4.5.2
    • 圧縮対象のフォルダの内容 (ログファイルが 1 万 (各ファイルサイズは 1 キロバイト前後))
      D:\test
      ┣ a-00001.log
      ┣ a-00002.log
      ┣ a-00003.log
      ...
      ┗ a-10000.log

Compress-Archive コマンドレット

当初 Compress-Archive コマンドレットを使用すれば良いと考え、下記のイメージで開発環境で実装し問題なかった。
Get-ChildItem "D:\test" | Get-ChildItem | Compress-Archive -DestinationPath "D:\test.zip"
しかし、運用環境で実行したところ下記のエラーが発生した。

PS D:\test> Get-ChildItem "D:\test" | Get-ChildItem | Compress-Archive -DestinationPath "D:\test.zip"
用語 'Compress-Archive' は、コマンドレット、関数、スクリプト ファイル、または操作可能なプログラムの名前として認識されま
せん。名前が正しく記述されていることを確認し、パスが含まれている場合はそのパスが正しいことを確認してから、再試行してく
ださい。
発生場所 行:1 文字:59
+ Get-ChildItem "D:\test" | Get-ChildItem | Compress-Archive <<<<  -DestinationPath "D:\test.zip"
    + CategoryInfo          : ObjectNotFound: (Compress-Archive:String) []、CommandNotFoundException
    + FullyQualifiedErrorId : CommandNotFoundException

Compress-Archive コマンドレットは PowerShell vrsion 5.0 で追加された機能だった。

代替え手段その1

stackoverflow などで同様の問題に取り組んでいる人たちの対処を真似して下記のように実装した。

$files = Get-ChildItem "D:\test"
$ZIP_FILE_NAME = "D:\test.zip"

#Prepare zip file
if(-not (test-path($ZIP_FILE_NAME))) {
    set-content $ZIP_FILE_NAME ("PK" + [char]5 + [char]6 + ("$([char]0)" * 18))
    (dir $ZIP_FILE_NAME).IsReadOnly = $false  
}

$shellApplication = new-object -com shell.application
$zipPackage = $shellApplication.NameSpace($ZIP_FILE_NAME)

foreach($file in $files) { 
    $zipPackage.CopyHere($file.FullName)
    while($zipPackage.Items().Item($file.name) -eq $null){
        Start-sleep -milliseconds 500
    }
}

代替え手段その1の実行結果

運用環境にて実行したところ 1,000 件を超えたところでエラーもなく圧縮処理が止まってしまった。

CopyHere() でのファイル追加と圧縮の処理が走った際に十分な sleep 値ではなかったため、圧縮中に次のファイルにアクセスしようとして何かしらのエラーが発生したものだと想定される。

sleep 値を十分に大きな数値にすれば発生頻度は下がると思われるが、仮に 10 秒を設定した場合 1 ファイルの圧縮に必ず 10 秒かかってしまうのでファイル数が多い場合は不要な時間がかかってしまうので当該手段は不採用とした。

代替え手段その2

オープンソースの DotNetZip (Ionic.Zip.dll) を使用する。
DotNetZip を動作させるには .NET Framework 2.0 以上が必要
取得方法は下記を参考にした。
 https://plaza.rakuten.co.jp/satocchia/diary/201807270000/
実装については下記を参照して作成した。
 https://gist.github.com/daicham/4528511

# DotNetZip での圧縮処理
$files = Get-ChildItem "D:\test"
$ZIP_FILE_NAME = "D:\test.zip"
[void][System.Reflection.Assembly]::LoadFrom(".\Ionic.Zip.dll")
# デフォルトだと日本語ファイル名のファイルが無視されてしまうので Encoding で shift_jis を指定する
$Encoding = [System.Text.Encoding]::GetEncoding("shift_jis")
$zipfile =  new-object Ionic.Zip.ZipFile($Encoding)
foreach($file in $files) { 
    $zipfile.AddFile($file, "")
}
$zipfile.Save($ZIP_FILE_NAME)
$zipfile.Dispose()

代替え手段その2の実行結果

運用環境にて実行したところ 10,000 ファイルの圧縮に成功した。