複数のファイルを 右クリックからファイル単位で圧縮するバッチ


概要

Windowsのエクスプローラ上でファイルを圧縮したいとき、
右クリックから「送る」メニュー内の「圧縮 (zip 形式) フォルダー」を選択すれば簡単に圧縮できます。

しかし、複数のファイルを個々に圧縮したい場合はどうでしょう?

複数ファイルを選択してそのまま右クリックから圧縮すると、まとめて一つのZipファイルに圧縮されてしまいます。
そのため、ファイルを一つ一つ選択して右クリックから...、などとやっていかなくてはなりません。

そこで、PowerShellスクリプトで上記を解決しました。

このPowerShellスクリプトのポイントは、次に挙げる2点です。

  • ファイル圧縮ソフト等を新たに導入せず、素のWindowsで実現している点
  • 右クリックの「送る」メニューから使用することを想定した設計である点

環境

Windows10, 7

使用方法

  • オススメの使用方法は、「送る」メニューに "ZipFile.bat" をショートカットファイルとして登録しておき、圧縮したいファイルまたは圧縮したいフォルダを右クリックして「送る」メニューから使用することです。

  • 通常の使用方法は、リネームしたいファイル自体またはファイルを格納したフォルダを選択し、"ZipFile.bat" にDrag&Dropします(ファイルまたはフォルダのパスが引数に指定されてバッチが起動します)。

  • ファイルへのD&Dが困難な場合は、バッチファイルを単体で起動するとフォルダパスの入力を求められるため、ファイルまたはフォルダをD&Dして [Enter]キーを押下します。ファイルパス(改行区切りで複数可能)をペーストする方法もあります。

補足事項

  • Zipファイルの出力先はカレントディレクトリとなります。すなわち、プロンプト画面開始時は通常、ユーザーフォルダがカレントディレクトリであるため、既定ではここが出力先となります。CD コマンド (Change Directory) により出力先を変更可能です。
    ショートカットファイルから使用する場合、第1引数に "-out"、第2引数にディレクトリパスを記述することで出力先を指定可能です。

ソースファイル

「マジック生成」するには、本ページ全体を選択してコピー後にB642FHT.batを起動して下さい。
その後、生成したZIPファイルを解凍して任意の場所へ配置して下さい。

コード

ZipFile.bat
@ECHO OFF
PowerShell -ExecutionPolicy RemoteSigned -File %~dpn0.ps1 %* "nul" "nul"
EXIT /B
ZipFile.ps1
$Host.UI.RawUI.ForeGroundColor = "Green"

"############# ZipFile.ps1 ##############"
"# ZIP compressing each files           #"
"#                                      #"
"#   1st release: 2019-07-15            #"
"#   Last update: 2019-07-15            #"
"#   See the web for more information   #"
"#   https://qiita.com/x-ia             #"
"########################################"
"# PS compressing and shell objects"
"# http://qshino.hatenablog.com/entry/2017/03/16/210106"
"#   Quoted on  : 2019-07-14"
"########################################"

$nameFileScript = (Get-ChildItem $MyInvocation.MyCommand.Path).BaseName
$dirPathScript = Split-Path $MyInvocation.MyCommand.Path -Parent
$extZip = ".zip"
$extLog = ".log"
$Host.UI.RawUI.WindowTitle = $nameFileScript
$flagCont = 1
$pathFileLog = $dirPathScript + "\" + $nameFileScript + $extLog

$iArg = 0
$numIn = 0
$numOut = 0

# Set option to determine output directory
if (($Args[0] -like "-out*") -And (Test-Path $Args[1])) {
  $pathDirZip = $Args[1]
  $iArg = 2
} else {
  $pathDirZip = (Convert-Path .)
}
Write-Host "`r`nDirectory path to output zip files:`r`n$pathDirZip"

# Set no-continue
if ($Args[$iArg] -ne "nul") {
  $flagCont = 0
}

do {
  $arg = $Args[$iArg]
  if ($arg -eq "nul") {
    # Input src dir and output file
    Write-Host "`r`nEnter the directory path to zip."
    Write-Host "To exit, hit the Enter key w/o any characters."
    $arg = Read-Host
    if (($arg -eq $null) -Or ($arg -eq "")) {
      Write-Host "`r`nTerminated by user.`r`n"
      $strResult = "Terminate(user)"
      break
    }
    if ((Test-Path $arg) -ne $true) {
      Write-Host "`r`nFile not exists.`r`n"
      continue
    }
  } else {
    Write-Host "`r`nThe file/directory to be archived:`r`n$arg`r`n"
  }

  # Set path variables
  $pathDirSrc = $arg
  $nameDirSrc = [System.IO.Path]::GetFileName($pathDirSrc)
  $pathFileZip = $pathDirZip + "\" + $nameDirSrc + $extZip
  ++$numIn

  while ($true) {
    if ((Test-Path $pathDirSrc) -ne $true) {
      Write-Host "The files to be archived not exists.`r`n"
      $strResult = "Failure(source)"
      break
    }

    if ((Test-Path $pathFileZip) -eq $true) {
      Write-Host "The file to output already exists.`r`n"
      $strResult = "Failure(output)"
      break
    }

    # Set ZIP header an empty ZIP file
    "PK"+ [char]5 + [char]6  + ("$([char]0)"*18) | `
    New-Item -Path $pathFileZip -Type File | Out-Null

    # Create a shell object
    $objSh = New-Object -Com Shell.Application

    # Get an empty ZIP file
    $objZip = $objSh.NameSpace($pathFileZip)

    # hash
    $hash = @{}
    $numCnt=0
    foreach($eleDir in ($pathDirSrc | %{Get-Item $_})){
      if ( $hash.ContainsKey($eleDir) ){continue}
      $hash[$eleDir] = $true
      $numCnt++
      $objZip.CopyHere($eleDir.FullName)
    }

    # Wait until all items are archived
    while($true){
      Start-Sleep -milliseconds 400
      if ($numCnt -le $objZip.Items().Count) {
        break
      }
    }

    $strResult = "Success($numCnt)"
    Write-Host "Done archiving.`r`n"

    # Destroy shell application
    [System.Runtime.Interopservices.Marshal]::ReleaseComObject($objSh) | Out-Null
    Remove-Variable objSh
    ++$numOut
    break
  }

  # Output log
  $timeStampNow = (Get-Date).ToString("yyyy-MM-dd HH:mm:ss.fff")
  "$timeStampNow`t$nameFileScript`t$strResult`t$pathDirSrc" | `
  Out-File $pathFileLog -Encoding Default -Append
  $Host.UI.RawUI.WindowTitle = "$nameFileScript $numOut/$numIn"

  if ($Args[$iArg] -ne "nul") {
    ++$iArg
  }
} while (($($Args[$iArg]) -ne "nul") -Or ($flagCont -eq 1))

バイナリ (Base64 encoding)

ZipFile.zip
---
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
---