こんにちは。

キャスレーコンサルティングのSI(システム・インテグレーション)部:梅澤です。
先日、私の現場でこのようなスクリプト作成依頼がありました。

  • 「外部システムから受信したデータファイルを圧縮して保管したい。」
  • 「初期状態のPCに導入されているものだけで作成して欲しい。」

そんな都合の良いソフトウェアがあるのだろうか・・・と探していたところに、
偶然出合ったものが Windows PowerShell でした。

そういった経緯もあり、今回は、Windows PowerShellを使ってZIPファイルの作成に挑戦してみました。

Windows PowerShellの概要・特徴

:概要 Windows PowerShell は、マイクロソフト社が開発したコマンドラインシェルです。

:特徴 .Net Frameworkを基盤として、構築されているので.Net Frameworkのライブラリが使えます。 Windows 7 以降のOSには標準搭載されています。 バッチよりも複雑な処理が実現可能です。

プログラムの処理概要

フォルダを指定し、更新日時が指定した年月日以前のファイルをZIPファイルとして圧縮する。

引数として「ZIPファイルの作成先、圧縮するファイルの格納されたフォルダ、圧縮対象とする年月日」を指定します。

プログラムの動作確認

実際にプログラムの動作を見てみます。

今回作成したプログラムのフォルダ構成は画像のようになっております。

tech_blog_1

ファイルについて説明します――

  • ZIP_SCRIPT.ps1

圧縮スクリプト本体です。 PowerShellで作成したスクリプトの拡張子を.ps1として保存します。

  • ZIP_SCRIPT_START.bat

ZIP_SCRIPT.ps1を呼び出すためのbatファイルです。 PowerShellスクリプトを直接実行するためにはいくつかの手順を踏む必要があるので、 batファイルを利用して手順の自動化を図っています。

  • INPUT

今回、圧縮するファイルが格納されたフォルダです。 INPUTフォルダには以下のファイルが格納されています。

tech_blog_WPS_2

では、batファイルを実行してスクリプトの動作を確認していきます。 ZIP_SCRIPT_START.batを実行すると以下の画面が表示されます。

tech_blog_WPS_3

batファイル内で引数として指定した内容が赤枠部分に表示されます。 キーを押下して、処理を続行します。

処理を続行すると、赤枠部分のメッセージが続けて表示されます。

tech_blog_WPS_4

このままキーを押下すると、cmd.exeが終了します。 以上で、処理は終了です。

作成されたZIPファイルを確認します。

tech_blog_WPS_5

スクリプトと同じ階層にZIPファイルが作成されていることが確認できます。

ZIPファイルに更新日時が指定した年月日以前のファイルのみが格納されているか確認します。

tech_blog_WPS_6

ファイル更新日時が指定した年月日「2016/10/12」以前のファイルのみが格納されていますね。

◆ ファイル圧縮前

tech_blog_WPS_7

◆ ファイル圧縮後

tech_blog_WPS_8

 異常終了時の動作も確認します。

◆ 同名のZIPファイルが既に存在していた場合

tech_blog_WPS_9

◆ 圧縮対象フォルダが存在していなかった場合

tech_blog_WPS_10

それぞれ赤枠部分のメッセージが表示されることが確認できました。

ソースコードの確認

ソースコードを見ながら、処理の過程を確認します。 今回作成したプログラムのソースコードは以下の2点となります。

◆ ZIP_SCRIPT_START.bat


@echo off

rem カレントディレクトリの変更
cd /d %~dp0

rem powershell 実行ポリシー取得
powershell Set-ExecutionPolicy RemoteSigned

rem zipファイル出力先の設定
set ZIP_FILEPATH=.\TEST_ZIP.zip

rem 圧縮したいファイルの格納されたフォルダの指定
set TARGET_FOLDER=.\INPUT

rem 日時の指定
set DATE=20161021

echo ------------------------------------------
echo ファイル圧縮スクリプト
echo ------------------------------------------
echo.
echo 下記の設定でスクリプトを実行します
echo.
echo 圧縮ファイル出力先:%ZIP_FILEPATH%
echo 圧縮対象フォルダ :%TARGET_FOLDER%
echo 圧縮対象日時      :%DATE%
echo.
echo ------------------------------------------
echo.
pause
echo.

rem ファイル圧縮スクリプト実行
powershell .\ZIP_SCRIPT.ps1 %ZIP_FILEPATH% %TARGET_FOLDER% %DATE% ;exit $LASTEXITCODE

pause
echo.
echo ------------------------------------------
echo 処理結果
echo ------------------------------------------
echo.
if 0 NEQ %ERRORLEVEL% (
    echo %ERRORLEVEL%件のファイルを圧縮しました

) else (
    echo ファイル圧縮に失敗しました
)
echo.
echo ------------------------------------------
echo.
pause

rem powershell 実行ポリシー初期化
powershell Set-ExecutionPolicy Restricted

◆ ZIP_SCRIPT.ps1

#カウンタ(ファイル圧縮件数)を設定
$cnt = 0

#出力先ZIPファイルの存在チェック
if(-not (test-path $args[0])){

    #入力ファイルの存在チェック
    if(test-path $args[1]){

        #出力先ZIPファイルパス取得
        $zipfilepath = $args[0]
        #入力フォルダパスを取得
        $filepath = $args[1]
        #入力フォルダ内のファイルを取得
        $folder = get-childItem $args[1]
        #圧縮対象日付を取得
        $date = $args[2]

        #ZIPファイルの作成
        set-content $zipfilepath ("PK" + [char]5 + [char]6 + ("$([char]0)" * 18 ))
        (dir $zipfilepath).IsReadOnly = $false
        #シェルオブジェクト生成
        $shellApplication = new-object -com shell.application
        #作成したZIPファイルの取得
        $zipPackage = $shellApplication.NameSpace($zipfilepath)

        foreach($file in $folder){
            #入力ファイルの更新日付を取得
            $timestamp = $(get-itemProperty $filepath\$file).LastWriteTime.ToString('yyyyMMdd')
            #圧縮対象日付けと入力ファイル更新日時の比較
            if($date -ge $timestamp){

                #入力ファイルをZIPファイルへ格納
                $zipPackage.CopyHere($file.Fullname)
                #ZIPファイルに格納されるまで、後続処理を待機
                while(($cnt + 1) -ne $zipPackage.Items().Count) {
                    start-sleep -milliseconds 500
                }
                #カウンタを更新
                $cnt ++
                #圧縮成功メッセージ表示
                write-host "ファイルを圧縮しました。:$filepath\$file"
            }
        }
    } else {
        #メッセージ表示
        write-host "指定されたフォルダが見つかりませんでした。"
    }

} else {
    #メッセージ表示
    write-host "同名のZIPファイルが既に存在しています。"

}
#カウンタを呼び出し元に返却
exit $cnt

batファイルの方からコードの流れを確認します。 batファイルでは、主にPowerShell関連のコマンドに絞って確認していきたいと思います。

rem powershell 実行ポリシー設定
powershell Set-ExecutionPolicy RemoteSigned

PowerShellは、セキュリティのため、実行ポリシーという形式で.ps1の実行が制御されています。
デフォルト設定の実行ポリシーでは、.ps1の実行が禁止されているため「Set-ExecutionPolicy」で.ps1を実行可能な実行ポリシーに設定しています。

実行ポリシーには4種類あり、以下のように設定されています。

実行ポリシー ローカル リモート
Restricted × ×
AllSinged
RemoteSinged
Unrestricted

※ △は、署名されているものであれば実行可能という意味です。

今回は、ローカルの.ps1ファイルを実行したいので実行ポリシーを「RemoteSinged」に変更しました。
デフォルト設定では、全ての.ps1が実行できない「Restricted」になっています。

rem powershell 実行ポリシー初期化
powershell Set-ExecutionPolicy Restricted

実行ポリシーをデフォルトの状態に戻しています。
処理が完了したら、実行ポリシーはデフォルト設定に戻しておくほうが良いと思います。

rem zipファイル出力先の設定
set ZIP_FILEPATH=.\TEST_ZIP.zip

rem 圧縮したいファイルの格納されたフォルダの指定
set TARGET_FOLDER=.\INPUT

rem 日時の指定
set DATE=20161021
(途中省略)
rem ファイル圧縮スクリプト実行
powershell .\ZIP_SCRIPT.ps1 %ZIP_FILEPATH% %TARGET_FOLDER% %DATE% ;exit $LASTEXITCODE

PowerShellで作成されたスクリプトを実行しています。
「powershell “実行するスクリプト” “引数1” “引数2” ……」とする事で、
実行するスクリプトに引数を渡すことができます。

batファイルからPowerShellスクリプトを実行し、戻り値を取得する場合、
戻り値はERRORLEVELという変数に格納されます。
通常、PowerShellからbatファイルに戻り値を返す場合、
「0」か「1」しか返すことが出来ないのですが「exit $LASTEXITCODE」と追記する事で
それ以外の数値を返す事が出来るようになります。

今回は、圧縮したファイルの件数を取得するために「exit $LASTEXITCODE」を設定しています。

batファイルの説明は以上となります。
では、PowerShell側のソースコードを見て行きたいと思います。

#出力先ZIPファイルの存在チェック
if(-not (test-path $args[0])){

    #入力ファイルの存在チェック
    if(test-path $args[1]){
(途中省略)
    } else {
        #メッセージ表示
        write-host "指定されたフォルダが見つかりませんでした。"

    }

} else {
    #メッセージ表示
    write-host "同名のZIPファイルが既に存在しています。"

}

2行目、5行目のtest-pathは、ファイルの存在確認をするコマンドレットです。
「test-path ファイルパス」とすることで、そのファイルの存在確認を行うことが出来ます。
ファイルが存在する場合はTrue、存在しない場合はFalseが返却されます。
if文と組み合わせて使う事が多いと思います。

今回の場合、2行目ではZIPファイルの存在確認を、
5行目では圧縮ファイルの格納されたフォルダの存在確認を行っております。
ZIPファイルの確認では、既にZIPファイルが存在していた場合、エラーとなって欲しいので
「if(-not (条件式))」とすることで、真となる条件を反転させています。

7行目、8行目のwrite-hostは、コンソールウィンドウにメッセージを表示するコマンドレットです。
「write-host ”表示するメッセージ”」とすることで、メッセージが表示できます。
write-hostは、-ForegroundColorと-BackGroundColorの2つのパラメータを持っており、
これらを設定することで、文字色と背景色を変更することが出来ます。

        #ZIPファイルの作成
        set-content $zipfilepath ("PK" + [char]5 + [char]6 + ("$([char]0)" * 18 ))
        (dir $zipfilepath).IsReadOnly = $false
        #シェルオブジェクト生成
        $shellApplication = new-object -com shell.application
        #作成したZIPファイルの取得
        $zipPackage = $shellApplication.NameSpace($zipfilepath)

2行目のset-contentは、ファイルにテキストを出力するコマンドレットです。
「set-content ファイル テキスト」とすることで指定したファイルにテキストを出力することが出来ます。
また、指定したファイルが存在しなかった場合、ファイルを新規に作成しテキストを出力します。
今回はset-contentコマンドレットを用いて、新規に作成されたZIPファイルにZIPファイルのヘッダ部を書き込んでいます。

5行目のnew-objectはオブジェクトを生成するコマンドレットです。
「new-object オブジェクト名」とすることで指定したオブジェクトを生成します。
今回は、作成したZIPファイルを取得するためにシェルオブジェクトを生成しています。

        foreach($file in $folder){
            #入力ファイルの更新日付を取得
            $timestamp = $(get-itemProperty $filepath\$file).LastWriteTime.ToString('yyyyMMdd')
            #圧縮対象日付けと入力ファイル更新日時の比較
            if($date -ge $timestamp){

                #入力ファイルをZIPファイルへ格納
                $zipPackage.CopyHere($file.Fullname)
                #ZIPファイルに格納されるまで、後続処理を待機
                while(($cnt + 1) -ne $zipPackage.Items().Count) {
                    start-sleep -milliseconds 500
                }
                #カウンタを更新
                $cnt ++
                write-host ファイルを圧縮しました。:$filepath\$file
            }

        }

3行目の「get-itemProperty」は指定したファイルのレジストリ情報を取得するコマンドレットです。
指定した年月日と比較を行うため、今回はファイルの更新日時を取得する目的で利用しています。

8行目で、圧縮対象ファイルをZIPファイルへコピーしています。
CopyHereは非同期で動作するため、ZIPファイルへのコピーが完了していなくても
後続の処理へと移行していまいます。
そのため、10行目~12行目のループ処理によって後続を待機させています。

11行目の「start-sleep」は指定した時間だけスクリプトの動作を一時停止するコマンドレットです。

カウンタに1加算した値とZIPファイルに格納されたファイル数が等しくなるまで
0.5秒単位で処理を一時停止しています。

#カウンタを呼び出し元に返却
exit $cnt

最後に、exitで圧縮したファイル数をbatファイルに返却しています。
batファイル側の以下のコードを確認します。

echo ------------------------------------------
echo 処理結果
echo ------------------------------------------
echo.
if 0 NEQ %ERRORLEVEL% (
    echo %ERRORLEVEL%件のファイルを圧縮しました

) else (
    echo ファイル圧縮に失敗しました
)
echo.
echo ------------------------------------------
echo.

batから呼び出したPowerShellスクリプトの戻り値はERRORLEVELに格納されるので、
ERRORLEVELの値を見て、実行結果の成否を確認しています。

値が0ではなかったら成功、0だったら失敗としてメッセージを表示します。

以上で、このプログラムの処理は終了です。

最後に

いかがでしたでしょうか?

Windows PowerShellの利点はこのような複雑な処理をPowerShell単体で行うことが出来る点だと思います。
また、Windows 7 からは標準搭載された機能ですので、
特別なソフトウェアをインストールする必要が無いというのも利点の一つだと思います。

「複雑な処理をスケジューラに組み込まなくてはいけなくなったけど、ツールやソフトウェアのインストールが禁止されている。」なんて
シチュエーションに遭遇したら、PCのOSを確認してみて下さい。
そのPCがWindows 7以降のOSであったら、Windows PowerShellが貴方の助けになるかもしれません。

ご閲覧頂き有難う御座いました。