前回は1つのPowerShellスクリプトから棒グラフと円グラフの双方を生成できるようにスクリプトを書き換えた。今回は、このスクリプトに対するラッパースクリプトを2つ作成する。目的は、コードを集約してメンテナンス性を上げることと、コマンドに分けることで使いやすくすることの2つだ。
→連載「PowerShell Core入門 - 基本コマンドの使い方」の過去回はこちらを参照。
メンテナンス性と扱いやすさとの両立
PowerShellスクリプトのメンテナンスという点で考えると、書き換え対象となるPowerShellスクリプトは1つである方が好ましい。ほとんど同じようなPowerShellスクリプトのファイルがたくさんあったら、書き換えが必要になったときにそれらを全部書き換えなければならない。似たようなスクリプトはできるだけまとめておいた方がメンテナンスはしやすくなる。
反面、汎用的なPowerShellスクリプトにすると、挙動を変えるためにパラメータを追加することになり、パラメータまみれのPowerShellスクリプトが出来上がってしまう。これはこれで使いづらい。いま行っている作業で言えば、前回まとめた「csv2chart.ps1」は、生成するグラフの種類をパラメータで指定することになる。これは、見方によっては面倒だ。
コマンドとして「csv2barchart.ps1」や「csv2piechart.ps1」が用意されていれば、コマンド名で何をするかがわかるし、グラフの種類を指定するパラメータを使用する必要もない。ただし、PowerShellスクリプトを増やすのは上述の通り、メンテナンス面でマイナスだ。
そこで、「csv2barchart.ps1」や「csv2piechart.ps1」というPowerShellスクリプトを用意するが、これらは「csv2chart.ps1」を呼び出すだけのラッパースクリプトとする。こうすればメンテナンス性とコマンド名による簡素化の2つのメリットを享受できるのだ。
PowerShellラッパースクリプトを作成
以下に、今回の成果物である2つのPowerShellスクリプトを示す。
csv2barchart.ps1
#!/usr/bin/env pwsh
#========================================================================
# 引数を処理
# -CSVFile パス グラフのCSVデータファイルパス
# -PNGFile パス 生成するPNG画像のファイルパス
# -OutFile パス 中間生成されるHTMLファイルパス
# -GraphTitle タイトル グラフのタイトル
# -Width 幅 生成するPNG画像の幅
# -Height 高さ 生成するPNG画像の高さ
#========================================================================
Param(
[Parameter(Mandatory=$true)][String]$CSVFile,
[String]$PNGFile = (Get-Location).ToString() + '\out.png',
[String]$OutFile = (Get-Location).ToString() + '\out.html',
[String]$GraphTitle = 'グラフ',
[Int]$Width = 2000,
[Int]$Height = 1200
)
#========================================================================
# csv2chart.ps1を使ってグラフを生成
#========================================================================
csv2chart.ps1 `
-CSVFile $CSVFile `
-PNGFile $PNGFile `
-GraphTitle $GraphTitle `
-Width $Width `
-Height $Height `
-GraphType 'Bar'
csv2piechart.ps1
#!/usr/bin/env pwsh
#========================================================================
# 引数を処理
# -CSVFile パス グラフのCSVデータファイルパス
# -PNGFile パス 生成するPNG画像のファイルパス
# -OutFile パス 中間生成されるHTMLファイルパス
# -GraphTitle タイトル グラフのタイトル
# -Width 幅 生成するPNG画像の幅
# -Height 高さ 生成するPNG画像の高さ
#========================================================================
Param(
[Parameter(Mandatory=$true)][String]$CSVFile,
[String]$PNGFile = (Get-Location).ToString() + '\out.png',
[String]$OutFile = (Get-Location).ToString() + '\out.html',
[String]$GraphTitle = 'グラフ',
[Int]$Width = 2000,
[Int]$Height = 1200
)
#========================================================================
# csv2chart.ps1を使ってグラフを生成
#========================================================================
csv2chart.ps1 `
-CSVFile $CSVFile `
-PNGFile $PNGFile `
-GraphTitle $GraphTitle `
-Width $Width `
-Height $Height `
-GraphType 'Pie'
それぞれのラッパースクリプトはparam()でパラメータを処理して、それを「csv2chart.ps1」に渡して実行しているだけだ。長く使うにあたって必要になるのはインタフェースに永続性があることなので、つまりこの場合、param()で処理する内容に永続性があるかということになる。
なお、前回作成した「csv2chart.ps1」は稿末に付録として掲載してあるのでそれを参考にしてもらえればと思う。
動作を確認してみよう
では動作を確認する。これまでと同じ「data.csv」を使用する。
場所,気温
小河内,17.6
青梅,20.8
練馬,21.4
八王子,20.2
府中,20.7
東京,21.3
江戸川臨海,19.6
羽田,18.7
大島,19.8
大島北ノ山,19.2
新島,18.8
神津島,17.9
三宅島,19.0
三宅坪田,20.6
八重見ヶ原,21.2
八丈島,20.5
父島,24.2
まず、csv2barchart.ps1を次のように実行して棒グラフを生成する。
生成された棒グラフは次のとおり。
次に、csv2piechart.ps1を実行して円グラフを生成する。
生成された円グラフは次の通りだ。
こんな感じで、それぞれのコマンド(PowerShellスクリプト)からグラフ画像が生成されたことが確認できる。
* * *
PowerShellでスクリプトをたくさん作るようになると、「どのように整理するのか」がポイントになってくる。
PowerShellスクリプトの良いところの一つは、気軽に使える機能を作成できることだ。最初は整理のことなどは考えずに、シンプルなものを作っていくことをお勧めしたい。最初から気張って作成するとうまくいかなかったり、無駄に複雑になって後からの書き換えが面倒になったりする。いつでも書き換えられるような状態にしておくことが大切だ。
ある程度PowerShellスクリプトのサイズが大きくなってきたり、似たようなPowerShellスクリプトが増えてきたりしたら、統合することを考えよう。統合してからラッパースクリプトを作成すれば、メンテナンス性とこれまでのコマンドとの互換性も確保できる。
ここまでに取り上げてきた開発の手順はほかのPowershellスクリプトを作成する際にもある程度使えるはずなので、参考にしてもらえればと思う。
付録
csv2chart.ps1
#!/usr/bin/env pwsh
#========================================================================
# 引数を処理
# -CSVFile パス グラフのCSVデータファイルパス
# -PNGFile パス 生成するPNG画像のファイルパス
# -OutFile パス 中間生成されるHTMLファイルパス
# -GraphTitle タイトル グラフのタイトル
# -Width 幅 生成するPNG画像の幅
# -Height 高さ 生成するPNG画像の高さ
# -GraphType グラフ種類 生成するグラフの種類
#========================================================================
Param(
[Parameter(Mandatory=$true)][String]$CSVFile,
[String]$PNGFile = (Get-Location).ToString() + '\out.png',
[String]$OutFile = (Get-Location).ToString() + '\out.html',
[String]$GraphTitle = 'グラフ',
[Int]$Width = 2000,
[Int]$Height = 1200,
[ValidateSet('Bar','Pie')]$GraphType = 'Bar'
)
#========================================================================
# 気温データを取得
#========================================================================
$CsvData = Import-Csv $CSVFile -Header 1,2
# ヘッダを変数に格納
foreach ($Row in $CsvData) {
$CsvHeader1 = $Row.1
$CsvHeader2 = $Row.2
break
}
#========================================================================
# Google Charts用HTMLの用意
#========================================================================
$GoogleChartsHTML1 = @"
<html>
<head>
<script type="text/javascript" src="https://www.gstatic.com/charts/loader.js"></script>
<script type="text/javascript">
// Google Visualization APIおよびコアチャートパッケージを読み込み
google.charts.load('current', {'packages':['corechart']});
// Google Visualization API読み込み完了後に実行
google.charts.setOnLoadCallback(drawChart);
function drawChart() {
// データテーブルを作成
var data = new google.visualization.DataTable();
data.addColumn('string', '');
data.addColumn('number', '$CsvHeader2');
data.addRows([
"@
$GoogleChartsHTML2 = @"
]);
// チャートオプションを設定
var options = {'title':'$GraphTitle',
'width':'$Width',
'height':'$Height'};
// 初期化およびチャートの生成
var chart = new google.visualization.${GraphType}Chart(document.getElementById('chart_div'));
// チャートイメージをbase64エンコードされたPNG画像データとして出力
google.visualization.events.addListener(chart, 'ready', function () {
document.getElementById('chart_base64').innerHTML = chart.getImageURI();
});
// チャートを描画
chart.draw(data, options);
}
</script>
</head>
<body>
<div id="chart_div"></div>
<div id="chart_base64"></div>
</body>
</html>
"@
#========================================================================
# CSVデータをGoogle Chartsで使える形式へ加工
#========================================================================
$GraphData = ""
$FirstLineIs = $true
foreach ($Row in $CsvData) {
if ($FirstLineIs) {
$FirstLineIs = $false
continue
}
$GraphData = $GraphData + "['" + $Row.1 + "'," + $Row.2 + "],"
}
$GraphData = $GraphData.trim(",")
#========================================================================
# Google Charts用のHTMLを出力
#========================================================================
$GoogleChartsHTML1 > $OutFile
$GraphData >>$OutFile
$GoogleChartsHTML2 >>$OutFile
#========================================================================
# WebDriver起動
#========================================================================
webdriver_edge_start.ps1
#========================================================================
# Microsoft EdgeでGoogle Charts用のHTMLをオープン
#========================================================================
$FileURL = "file:///" + $OutFile.Replace('\','/')
'Microsoft Edgeでグラフを描画します。'
Set-SeUrl -Url $FileURL
#========================================================================
# 描画されたグラフをBase64エンコードされたPNGデータとして取得
#========================================================================
'描画したグラフをBase64エンコードされたPNGデータとして取得します。'
$Element = Get-SeElement -By XPath -Value '//*[@id="chart_base64"]'
$Base64 = $Element.Text -replace 'data:image/png;base64,',''
#========================================================================
# Base64エンコードデータをデコードしてファイルへ保存
#========================================================================
'Base64エンコードされたPNGデータをデコードして保存します。'
$Bytes = [Convert]::FromBase64String($Base64)
[IO.File]::WriteAllBytes($PNGFile, $Bytes)
#========================================================================
# WebDriverを終了
#========================================================================
webdriver_edge_stop.ps1
#========================================================================
# 作業用の一時ファイルを削除
#========================================================================
Remove-Item $OutFile
webdriver_edge_start.ps1
#!/usr/bin/env pwsh
#========================================================================
# Microsoft Edge WebDriverを起動する
#========================================================================
#========================================================================
# 動作しているMicrosoft Edge WebDriverをすべて終了
#========================================================================
webdriver_edge_stop.ps1
#========================================================================
# Seleniumモジュールがない場合にはインストール
#========================================================================
if (-Not (Get-InstalledModule -Name Selenium 2> $Null)) {
'Seleniumモジュールをインストールします。'
Install-Module -Name Selenium -AllowPrerelease -Force
Get-InstalledModule -Name Selenium
}
#========================================================================
# Microsoft Edge WebDriverを起動
#========================================================================
'Microsoft Edge WebDriverを起動します。'
$Size = '1200,800'
if (-Not (Start-SeDriver -Browser Edge -Size $Size 2> $Null 3> $Null))
{
#================================================================
# Microsoft EdgeとMicrosoft Edge WebDriverのバージョンが一致して
# いないためにドライバが動作しなかった可能性がある。
#================================================================
#================================================================
# 不要なドライバプロセスを終了
#================================================================
webdriver_edge_stop.ps1
#================================================================
# Microsoft Edgeのバージョン番号
#================================================================
$EdgeDir='C:\Program Files (x86)\Microsoft\Edge\Application\'
$EdgeVersion=( Get-ChildItem -Name $EdgeDir |
Where-Object { $_ -NotMatch "[a-zA-Z]+" } |
Select-Object -First 1 )
# ↑ 【Select-Object -First 1の理由】
# 更新前のバージョンと更新後のバージョンが同時に
# 存在するタイミングがあるので、更新後のバージョン
# のみを取得するためにSelect-Objectを実行している。
#================================================================
# Microsoft Edge WebDriverダウンロードURLとデプロイ先パス
#================================================================
$DriverURL="https://msedgedriver.azureedge.net/$EdgeVersion/edgedriver_win64.zip"
$SeModVer=(Get-InstalledModule -Name Selenium).Version -replace "-.+$",""
$DriverDir="$env:HOME\Documents\powershell\Modules\Selenium\$SeModVer\assemblies"
$DriverDownloadDir="$DriverDir\_download"
#================================================================
# WebDriverダウンロード用の一時ディレクトリを作成
#================================================================
New-Item $DriverDownloadDir -ItemType Directory -Force
#================================================================
# Microsoft Edgeと同じバージョンのMicrosoft Edge WebDriverを
# ダウンロード
#================================================================
"Microsoft Edge WebDriver version $EdgeVersion をダウンロードします。"
curl -get `
-o $DriverDownloadDir\edgedriver_win64.zip `
$DriverURL
#================================================================
# Microsoft Edge WebDriverをデプロイ
#================================================================
"Microsoft Edge WebDriver version $EdgeVersion をインストールします。"
Expand-Archive -Path $DriverDownloadDir\edgedriver_win64.zip `
-Destination $DriverDownloadDir `
-Force
Copy-Item -Path $DriverDownloadDir\msedgedriver.exe `
-Destination $DriverDir\msedgedriver.exe `
-Force
#================================================================
# WebDriverダウンロード用の一時ディレクトリを削除
#================================================================
Remove-Item $DriverDownloadDir -Recurse -Force
#================================================================
# Microsoft Edge WebDriverを起動する
#================================================================
if (-Not (Start-SeDriver -Browser Edge -Size $Size 2> $Null 3> $Null))
{
#========================================================
# 原因不明の起動不能
#========================================================
#========================================================
# 不要なドライバプロセスを終了
#========================================================
webdriver_edge_stop.ps1
Exit
}
}
'Microsoft Edge WebDriverの起動処理完了。'
webdriver_edge_stop.ps1
#!/usr/bin/env pwsh
#========================================================================
# Microsoft Edge WebDriverを終了する
#========================================================================
#========================================================================
# WebDriverプロセスを終了
#========================================================================
if (Get-Process -Name msedgedriver 2> $Null)
{
'動作しているMicrosoft Edge WebDriverを終了します。'
Get-Process -Name msedgedriver 2> $Null
# Microsoft Edge WebDriverを終了
Stop-SeDriver 2> $Null
# まだ動作しているほかのMicrosoft Edge WebDriverを終了
if (Get-Process -Name msedgedriver 2> $Null)
{
Get-Process -Name msedgedriver 2> $Null | Stop-Process
}
'動作しているMicrosoft Edge WebDriverの終了処理完了。'
}