こんにちは。
キャスレーコンサルティングのSI(システム・インテグレーション)部:梅澤です。
先日、私の現場でこのようなスクリプト作成依頼がありました。
- 「外部システムから受信したデータファイルを圧縮して保管したい。」
- 「初期状態のPCに導入されているものだけで作成して欲しい。」
そんな都合の良いソフトウェアがあるのだろうか・・・と探していたところに、
偶然出合ったものが Windows PowerShell でした。
そういった経緯もあり、今回は、Windows PowerShellを使ってZIPファイルの作成に挑戦してみました。
Windows PowerShellの概要・特徴
:概要 Windows PowerShell は、マイクロソフト社が開発したコマンドラインシェルです。
:特徴 .Net Frameworkを基盤として、構築されているので.Net Frameworkのライブラリが使えます。 Windows 7 以降のOSには標準搭載されています。 バッチよりも複雑な処理が実現可能です。
プログラムの処理概要
フォルダを指定し、更新日時が指定した年月日以前のファイルをZIPファイルとして圧縮する。
引数として「ZIPファイルの作成先、圧縮するファイルの格納されたフォルダ、圧縮対象とする年月日」を指定します。
プログラムの動作確認
実際にプログラムの動作を見てみます。
今回作成したプログラムのフォルダ構成は画像のようになっております。
ファイルについて説明します――
- ZIP_SCRIPT.ps1
圧縮スクリプト本体です。 PowerShellで作成したスクリプトの拡張子を.ps1として保存します。
- ZIP_SCRIPT_START.bat
ZIP_SCRIPT.ps1を呼び出すためのbatファイルです。 PowerShellスクリプトを直接実行するためにはいくつかの手順を踏む必要があるので、 batファイルを利用して手順の自動化を図っています。
- INPUT
今回、圧縮するファイルが格納されたフォルダです。 INPUTフォルダには以下のファイルが格納されています。
では、batファイルを実行してスクリプトの動作を確認していきます。 ZIP_SCRIPT_START.batを実行すると以下の画面が表示されます。
batファイル内で引数として指定した内容が赤枠部分に表示されます。 キーを押下して、処理を続行します。
処理を続行すると、赤枠部分のメッセージが続けて表示されます。
このままキーを押下すると、cmd.exeが終了します。 以上で、処理は終了です。
作成されたZIPファイルを確認します。
スクリプトと同じ階層にZIPファイルが作成されていることが確認できます。
ZIPファイルに更新日時が指定した年月日以前のファイルのみが格納されているか確認します。
ファイル更新日時が指定した年月日「2016/10/12」以前のファイルのみが格納されていますね。
◆ ファイル圧縮前
◆ ファイル圧縮後
異常終了時の動作も確認します。
◆ 同名のZIPファイルが既に存在していた場合
◆ 圧縮対象フォルダが存在していなかった場合
それぞれ赤枠部分のメッセージが表示されることが確認できました。
ソースコードの確認
ソースコードを見ながら、処理の過程を確認します。 今回作成したプログラムのソースコードは以下の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が貴方の助けになるかもしれません。
ご閲覧頂き有難う御座いました。