■このサイトはPowerShell(MSH/Monad)奮闘記です
管理人「newpops(吉岡洋)」が「PowerShell(旧名:MSH/Monad)」の研究結果を日々綴っていきます。
【お知らせ】
この日記からPowerShellのTipsを抽出し「PowerShell FAQ」として整理しました。
2011-12-12
■[PowerShell] サーバールームの室温監視を行う <PowerShell Advent Calendar 2011>
はじめに
こんばんは。「PowerShell Memo」管理人のnewpopsこと吉岡洋です。
この度、牟田口さんが企画された「PowerShell Advent Calendar 2011」に参加することになりました。
本イベントの12日目を務めさせていただきます。
今回紹介するのは「サーバールームの室温監視を行う」スクリプトです。
作成の経緯
今から3年前の夏、とあるサーバルームのエアコンの温度センサーの不正動作で「時々、設定温度通りに室温が下がらない」という事象に遭遇しました。この「時々」というのが曲者で、通常は22℃前後に保たれているのですが、週に1回程度、センサーが温度を正しく検知できずに、温度が上昇してしまうのです。
気づくのが遅れると、室温が30℃に上昇することもあるとのこと。夜に問題が起こると、朝まで気づかないので厄介ですね。
運用責任者の方いわく、エアコン業者が調査しても原因が分からないとのことで、室温の監視ができないかと相談を受けました。
・・・ということで、PowerShellで室温の監視スクリプトを作る事にしました。
室温監視スクリプトの要件
- 室温の閾値(許容上限値)を設定できる
- サーバールームの室温を定期的に取得する
- 取得した温度をメールのサブジェクトに設定して、管理者に通知する
- 閾値超過に関係なく必ずメールを送る(スクリプトが稼働している確証が欲しい)
- 閾値温度を超えている場合は、「警報メール」を管理者に通知する
- 室温の取得に失敗した場合は「取得失敗メール」を管理者に通知する
- 室温を定期的にCSVに出力する
- 設定はファイルで定義できること
- 動作試験が容易であること
ハードウェア調達
まずは温度計ですが、ストロベリー・リナックス社の「USB温度・湿度計モジュール」を調達。
この製品は外部から温度モジュールにアクセスするためのDLL(USBMeter.dll)が付属しています。
また、組み立て済みの完成品で4980円と値段もお手頃です。
スクリプト作成におけるポイントと解説
本投稿の後方に掲載したスクリプトについて簡単に解説します。
定義ファイルはXML
今回、室温の閾値やメールの宛先など多くのものを定義ファイルに記述することになります。
私は、PowerShellでツールを作成する場合、定義ファイルは必ずXMLファイルにしています。
理由はPowerShellはXMLファイルへのアクセスが非常に容易であるからです。
$xml = [xml](Get-Content ./config.xml) $value = $xml.xxx.yyy.zzz
このような簡単な記述によりXMLの内容を「System.Xml.XmlDocument」型で取得し、値にアクセスできます。
Windows Native DLLへのアクセス
温度系モジュールに付属している「USBMeter.dll」は.Netで作成されたDLLではなく、C++で作成されたWindows Native DLLです。
PowerShellからWindows Native DLLにアクセスする方法はいくつかありますが、本スクリプトでは、USBMeter.dllへのアクセッサをVB.Netで記述し、VBCodeProviderクラスを用いて動的コンパイルするアプローチを採用しています。
動的コンパイルで得たアセンブリから、GetMethodメソッドで各メソッドへの参照を取得し、Invokeメソッドで実行することができます。
2つの起動モード
スクリプトの動作確認を容易にするために、以下の2つの起動モードを用意し、モード毎に定義ファイルを分けています。
また、起動時のモードの指定方法にはSwitchParameterを利用しています。
SwitchParameterはコマンドレットで多用されている方式で、以下の例では「-Recurse」がSwitchParameterです。
Get-ChildItem -Recurse
SwitchParameterはパラメータ指定の有無で分岐する実装が容易であるため、私は好んで利用しています。
メールの送信
メールの送信には「System.Net.Mail.SmtpClient」クラスを利用します。
ただし、本スクリプトでは、メール送信サーバに認証が不要な場合を想定しています。
もし、認証が必要な場合は、SmtpClientのCredentialsプロパティに適切な設定が必要です。
スクリプト
室温監視スクリプト本体(CheckTemperature.ps1)
1: ############################################################################### 2: # スクリプト名 :CheckTemperature.ps1 3: # 概要 :USB接続型温度計の監視を行う 4: # Powered by Hiroshi Yoshioka 5: ############################################################################### 6: # 詳細 7: # 8: # 以下の2つのモードがあり、モード毎に定義ファイルを分けています。 9: # 1.通常モード → 定義ファイル:config.xml 10: # 2.テストモード → 定義ファイル:config_test.xml 11: # 動作確認を行う場合は、config_test.xmlを修正し、 12: # テストモードで実行してください。 13: # 14: # 使用例 15: # 1.通常モードで起動する 16: # C:\> ./CheckTemperature.ps1 17: # 18: # 2.テストモードで起動する 19: # C:\> ./CheckTemperature.ps1 -Test 20: # 21: ############################################################################### 22: 23: Param([Switch]$Test) 24: 25: # 初期定義 26: $CONFIG_DIRNAME = 'config' 27: $CONFIG_FILENAME = 'config.xml' 28: $CONFIG_FILENAME_TEST = 'config_test.xml' 29: 30: $scriptDir = Split-Path $MyInvocation.MyCommand.Path -Parent 31: $CONFIG_DIR = Join-Path $scriptDir $CONFIG_DIRNAME 32: $CONFIG_PATH = Join-Path $CONFIG_DIR $CONFIG_FILENAME 33: $CONFIG_PATH_TEST = Join-Path $CONFIG_DIR $CONFIG_FILENAME_TEST 34: $CHECK_FAULT = -1 35: 36: # USBMetr.dllが提供する関数を利用可能にする 37: $provider = New-Object Microsoft.VisualBasic.VBCodeProvider 38: $params = New-Object CodeDom.Compiler.CompilerParameters 39: $params.GenerateInMemory = $True 40: $source = @' 41: Module USBMeter 42: 43: Public Declare Function GetVers Lib "USBMeter.dll" Alias "_GetVers@4" (ByVal dev As String) As String 44: Public Declare Function FindUSB Lib "USBMeter.dll" Alias "_FindUSB@4" (ByRef index As Integer) As String 45: Public Declare Function GetTempHumid Lib "USBMeter.dll" Alias "_GetTempHumid@12" (ByVal dev As String, ByRef temp As Double, ByRef humid As Double) As Integer 46: Public Declare Function ControlIO Lib "USBMeter.dll" Alias "_ControlIO@12" (ByVal dev As String, ByVal port As Integer, ByVal val_Renamed As Integer) As Integer 47: Public Declare Function SetHeater Lib "USBMeter.dll" Alias "_SetHeater@8" (ByVal dev As String, ByVal val_Renamed As Integer) As Integer 48: Public Declare Function GetTempHumidTrue Lib "USBMeter.dll" Alias "_GetTempHumidTrue@12" (ByVal dev As String, ByRef temp As Double, ByRef humid As Double) As Integer 49: 50: Public g_temp As Double 51: Public g_humid As Double 52: 53: Function GetVers_PS(ByVal dev As String) As String 54: GetVers_PS = GetVers(dev) 55: End Function 56: 57: Function FindUSB_PS(ByRef index As Integer) As String 58: FindUSB_PS = FindUSB(index) 59: End Function 60: 61: Function GetTempHumid_PS(ByVal dev As String) As Integer 62: GetTempHumid_PS = GetTempHumid(dev, g_temp, g_humid) 63: End Function 64: 65: Function ControlIO_PS(ByVal dev As String, ByVal port As Integer, ByVal val_Renamed As Integer) As Integer 66: ControlIO_PS = ControlIO(dev, port, val_Renamed) 67: End Function 68: 69: Function SetHeater_PS(ByVal dev As String, ByVal val_Renamed As Integer) As Integer 70: SetHeater_PS = SetHeater(dev, val_Renamed) 71: End Function 72: 73: Function GetTempHumidTrue_PS(ByVal dev As String) As Integer 74: GetTempHumidTrue_PS = GetTempHumidTrue(dev, g_temp, g_humid) 75: End Function 76: 77: End Module 78: '@ 79: 80: $compilerResults = $provider.CompileAssemblyFromSource($params, $source) 81: $assembly = $compilerResults.CompiledAssembly 82: $USBMeter = $assembly.GetType("USBMeter") 83: 84: # メソッド 85: $GetVers_PS = $USBMeter.GetMethod("GetVers_PS") 86: $FindUSB_PS = $USBMeter.GetMethod("FindUSB_PS") 87: $GetTempHumid_PS = $USBMeter.GetMethod("GetTempHumid_PS") 88: $ControlIO_PS = $USBMeter.GetMethod("ControlIO_PS") 89: $SetHeater_PS = $USBMeter.GetMethod("SetHeater_PS") 90: $GetTempHumidTrue_PS = $USBMeter.GetMethod("GetTempHumidTrue_PS") 91: 92: ############################################################# 93: # 関数名 Get-Temperature 94: # 概要 温度を取得する 95: # 引数 なし 96: # 戻り値 温度(取得できなかった場合は-1を返す) 97: # 戻り値型 Double 98: ############################################################# 99: function Get-Temperature() 100: { 101: $retFault = -1 102: 103: # 温度計のデバイス名を取得する 104: $device = $FindUSB_PS.Invoke($null, @(0)) 105: 106: # デバイス名が空文字の場合は -1 を返す。 107: if ($device -eq ''){return $retFault} 108: 109: # 温度/湿度を取得する 110: $ret = $GetTempHumidTrue_PS.Invoke($null, @($device)) 111: 112: # 取得に失敗した場合は -1 を返す。 113: if ($ret -ne 0){return $retFault} 114: 115: # 取得に成功した場合の処理 116: $temp = $USBMeter.GetField("g_temp").GetValue($null) 117: return $temp 118: } 119: 120: 121: ############################################################# 122: # 関数名 Send-Mail 123: # 概要 メールを送信する 124: # 引数 以下のスイッチの中から1つ指定する 125: # -Normal 正常メール 126: # -Alarm 警報メール 127: # -Fault 失敗メール 128: # 戻り値 なし 129: # 戻り値型 なし 130: ############################################################# 131: function Send-Mail($tempValue, [Switch]$Normal, [Switch]$Alarm, [Switch]$Fault) 132: { 133: if ($Normal.isPresent) 134: { 135: $mailConfig = $xml.USBMeter.NormalMail 136: } 137: elseif ($Alarm.isPresent) 138: { 139: $mailConfig = $xml.USBMeter.AlarmMail 140: } 141: elseif ($Fault.isPresent) 142: { 143: $mailConfig = $xml.USBMeter.FaultMail 144: } 145: 146: ## メール設定(共通) 147: $common = $xml.USBMeter.Common 148: $from = $common.Mail.From 149: $smtp = $common.Mail.Smtp 150: ## メール設定(個別) 151: # 送信アドレス 152: $OFS = ',' 153: $to = [String]$mailConfig.Address 154: $OFS = ' ' 155: # サブジェクト 156: $subject = $mailConfig.Subject 157: $tempStr = "{0,4:##0.0}℃" -F $tempValue 158: $subject = $subject.Replace('[TempValue]', $tempStr) 159: $date = Get-Date -Uformat "%Y/%m/%d" 160: $subject = $subject.Replace('[Date]', $date) 161: $time = Get-Date -Uformat "%H:%M:%S" 162: $subject = $subject.Replace('[Time]', $time) 163: 164: # メール送信 165: $mailer = New-Object System.Net.Mail.SmtpClient($smtp) 166: $mailer.Send($from, $to, $subject, "") 167: 168: # ログ出力 169: Write-Log $subject 170: 171: # CSV出力 172: Write-Csv $tempValue 173: } 174: 175: ############################################################# 176: # 関数名 Write-Log 177: # 概要 ログファイルに文字列を書き込む 178: # 引数 書き込む文字列 179: # 戻り値 なし 180: # 戻り値型 なし 181: ############################################################# 182: function Write-Log($msg) 183: { 184: $logMonth = Get-Date -Uformat "%Y%m" 185: $logDate = Get-Date -Uformat "%Y%m%d" 186: $now = Get-Date -Uformat "[%Y/%m/%d %H:%M:%S]" 187: $logfilePath = $logfilePathDef.Replace('[LogMonth]', $logMonth) 188: $logfilePath = $logfilePath.Replace('[LogDate]', $logDate) 189: 190: $logfileDir = Split-Path $logfilePath -Parent 191: if ((Test-Path $logfileDir) -eq $false){[void](md $logfileDir)} 192: $msg = "$now " + $msg 193: Write-Host $msg 194: $msg >> $logfilePath 195: } 196: 197: ############################################################# 198: # 関数名 Write-Csv 199: # 概要 CSVファイルに時刻と温度データを書き込む 200: # 引数 温度 201: # 戻り値 なし 202: # 戻り値型 なし 203: ############################################################# 204: function Write-Csv($temp) 205: { 206: $csvfileDir = Split-Path $csvfilePath -Parent 207: if ((Test-Path $csvfileDir) -eq $false){[void](md $csvfileDir)} 208: 209: $date = Get-Date -Uformat "%Y/%m/%d %H:%M:%S" 210: $data = [String]$date + "," + [String]$temp 211: $data | Out-File $csvfilePath -Append -Encoding Default 212: } 213: 214: ###################################################################### 215: # メイン 216: ###################################################################### 217: 218: # 各種設定を読み込む 219: if ($Test.isPresent) 220: { 221: # テストモードの場合 222: $xml = [xml](Get-Content $CONFIG_PATH_TEST) 223: } 224: else 225: { 226: # 通常モードの場合 227: $xml = [xml](Get-Content $CONFIG_PATH) 228: } 229: 230: # 温度の閾値 231: $tempThreshold = $xml.USBMeter.Temperature.Threshold 232: # チェック間隔(秒) 233: [int]$checkIntervalSec = $xml.USBMeter.CheckInterval.Second 234: # チェック間隔(分) 235: [String]$checkIntervalMin = "{0,4:0.00}" -F ($checkIntervalSec / 60) 236: 237: # ログファイルの相対パス 238: $logfilePathDef = Join-Path $scriptDir $xml.USBMeter.Common.Logfile 239: # CSVファイルの相対パス 240: $csvfilePath = Join-Path $scriptDir $xml.USBMeter.Common.Csvfile 241: 242: Write-Log '=========================================' 243: if ($Test.isPresent) 244: { 245: # テストモードの場合 246: Write-Log '◆◆◆テストモードで起動しました◆◆◆' 247: } 248: else 249: { 250: # 通常モードの場合 251: Write-Log '◆◆◆通常モードで起動しました◆◆◆' 252: } 253: Write-Log '=========================================' 254: Write-Log ' ■温度チェック:開始■' 255: Write-Log " 閾値:$tempThreshold ℃" 256: Write-Log " 間隔:$checkIntervalSec 秒($checkIntervalMin 分)" 257: Write-Log '=========================================' 258: 259: # 指定されたチェック間隔で、温度のチェックを行う 260: while($true) 261: { 262: # 温度を取得する 263: $temperature = Get-Temperature 264: # 温度の取得に失敗した場合は「失敗メールを送る」 265: if($temperature -eq $CHECK_FAULT) 266: { 267: $temperature = -1 268: Send-Mail $temperature -Fault 269: } 270: # 温度が閾値を超えているかチェックする 271: elseif ($temperature -gt $tempThreshold) 272: { 273: # 閾値を超えている場合は「警報メールを送る」 274: Send-Mail $temperature -Alarm 275: } 276: else 277: { 278: # 閾値を超えていない場合は「正常メールを送る」 279: Send-Mail $temperature -Normal 280: } 281: Start-Sleep -Seconds $checkIntervalSec 282: }
定義ファイル(./config/config.xml)
1: <?xml version="1.0" encoding="Shift_JIS"?> 2: <USBMeter> 3: <Temperature> 4: <Threshold>35</Threshold> 5: </Temperature> 6: 7: <CheckInterval> 8: <Second>900</Second> 9: </CheckInterval> 10: 11: <Common> 12: <Mail> 13: <From>'室温チェッカー'<admin@xxx.com></From> 14: <Smtp>xxx.smtp.com</Smtp> 15: </Mail> 16: <Logfile>log/[LogMonth]/Temperature_[LogDate].log</Logfile> 17: <CsvFile>csv/Temperature.csv</CsvFile> 18: </Common> 19: 20: <NormalMail> 21: <Subject>[温度]OK : [TempValue] : [Date] [Time]</Subject> 22: <Address>admin@xxx.com</Address> 23: <Address>yoshioka@xxx.com</Address> 24: </NormalMail> 25: 26: <AlarmMail> 27: <Subject>[温度]NG : [TempValue] : [Date] [Time]</Subject> 28: <Address>admin@xxx.com</Address> 29: <Address>yoshioka@xxx.com</Address> 30: </AlarmMail> 31: 32: <FaultMail> 33: <Subject>[温度]NG : 検出失敗 : [Date] [Time]</Subject> 34: <Address>admin@xxx.com</Address> 35: <Address>yoshioka@xxx.com</Address> 36: </FaultMail> 37: </USBMeter>
2010-09-10
■[PowerShell]久々に書き込み
お久しぶりです。
業務に没頭していたらあっという間に2年経ってしまいました。(^^;
業務でちょっとしたツールが欲しいときは、たいていPowerShellで作っています。
マイペースで紹介していこうと思います。
BASIC認証つきHTTPSページへのアクセス
わりと最近作ったものとしては、
BASIC認証つきHTTPSページにあるXMLに定期的にアクセスして、情報を取得するスクリプトですかね。
CA署名のない証明書のサイトへアクセスする際、処理に苦労しました。
Delegateを使わなければ実現できなかったので、
以下のページからDownloadできるNew-Delegate.ps1を利用させてもらいました。
http://www.microsoft.com/japan/windowsserver2008/countdown2008/powershell/default.mspx
2008-05-28
■[PowerShell][V2.CTP2]オセロゲームを作ろう(7):盤面描画とカーソル移動(2)
盤面描画とカーソル移動
前回紹介した、盤面描画とカーソル移動のサンプルについて解説します。
盤面描画
135 : # オセロ盤の描画 136 : function Draw-Board() 137 : { 138 : Write-Host "┌─┬─┬─┬─┬─┬─┬─┬─┐" -ForegroundColor $boardFgColor -BackgroundColor $boardBgColor 139 : for($i=1; $i -le 7;$i++) 140 : { 141 : Write-Host "│ │ │ │ │ │ │ │ │" -ForegroundColor $boardFgColor -BackgroundColor $boardBgColor 142 : Write-Host "├─┼─┼─┼─┼─┼─┼─┼─┤" -ForegroundColor $boardFgColor -BackgroundColor $boardBgColor 143 : } 144 : Write-Host "│ │ │ │ │ │ │ │ │" -ForegroundColor $boardFgColor -BackgroundColor $boardBgColor 145 : Write-Host "└─┴─┴─┴─┴─┴─┴─┴─┘" -ForegroundColor $boardFgColor -BackgroundColor $boardBgColor 146 : }
Draw-Init関数で最初の3行のメッセージを、
そこからコールされるDraw-Board関数でオセロ盤を描画します。
#139〜143行目はfor文を使っているので可読性が悪いですね。。。(^^;
結果はこんな感じ。
==================================
Othello by PowerShell
==================================
┌─┬─┬─┬─┬─┬─┬─┬─┐
│ │ │ │ │ │ │ │ │
├─┼─┼─┼─┼─┼─┼─┼─┤
│ │ │ │ │ │ │ │ │
├─┼─┼─┼─┼─┼─┼─┼─┤
│ │ │ │ │ │ │ │ │
├─┼─┼─┼─┼─┼─┼─┼─┤
│ │ │ │ │ │ │ │ │
├─┼─┼─┼─┼─┼─┼─┼─┤
│ │ │ │ │ │ │ │ │
├─┼─┼─┼─┼─┼─┼─┼─┤
│ │ │ │ │ │ │ │ │
├─┼─┼─┼─┼─┼─┼─┼─┤
│ │ │ │ │ │ │ │ │
├─┼─┼─┼─┼─┼─┼─┼─┤
│ │ │ │ │ │ │ │ │
└─┴─┴─┴─┴─┴─┴─┴─┘
カーソル移動
152 : # 指定した位置にカーソルを移動する 153 : function Move-Cursor([int]$newX, [int]$newY) 154 : { 155 : $coordinate = New-Object System.Management.Automation.Host.Coordinates $newX, $newY 156 : $rui.CursorPosition = $coordinate 157 : }
Move-Cursor関数は引数に指定した座標にカーソルを移動します。
引数は移動先のX座標、Y座標です。
座標を指定してCoordinatesオブジェクトを生成し、CursorPositionに代入すればOKです。
ちなみに、オセロ盤の左上のセルの座標は、X=2、Y=4(上から5行目、左から3byte目)です。
上下左右キー操作
46 : # 上下左右のキー操作 47 : if($keyCode -eq $keyMap::Up) {Move-CursorByDirection ([String]$keyMap::Up); continue} 48 : if($keyCode -eq $keyMap::Down) {Move-CursorByDirection ([String]$keyMap::Down); continue} 49 : if($keyCode -eq $keyMap::Left) {Move-CursorByDirection ([String]$keyMap::Left); continue} 50 : if($keyCode -eq $keyMap::Right){Move-CursorByDirection ([String]$keyMap::Right);continue}
キー入力の監視で解説した、Get-InputKey関数の中で上下左右キーを捕捉します。
上下左右キーのカーソル移動は処理が似ているので、Move-CursorByDirection関数に集約させており、
その際、引数として、Stringにキャストした$KeyMap(Keys列挙体)を渡しています。
159 : # 上下左右のキーを押した時のカーソル移動 160 : function Move-CursorByDirection([String]$key) 161 : { 162 : ($oldX, $oldY) = ($rui.CursorPosition.X, $rui.CursorPosition.Y) 163 : if($key -eq "Up") {$newX = $oldX; if($oldY -gt 0){$newY = $oldY - 1}} 164 : elseif($key -eq "Down") {$newX = $oldX; $newY = $oldY + 1} 165 : elseif($key -eq "Left") {if($oldX -gt 0){$newX = $oldX - 1}; $newY = $oldY} 166 : elseif($key -eq "Right"){$newX = $oldX + 1; $newY = $oldY} 167 : Move-Cursor $newX $newY 168 : }
Move-CursorByDirection関数では、まず162行目で現在のカーソル位置を取得。
163〜166行目で引数(Up,Down,Left,Rightの4種類)に応じて、カーソルの移動先を計算しています。
■[PowerShell][V2.CTP2]オセロゲームを作ろう(8):盤面描画とカーソル移動(3)
盤面描画とカーソル移動
引き続き、盤面描画とカーソル移動のサンプルについての解説です。
本アプリでは、エラーやデバッグ情報など、オセロ盤の下方にメッセージを表示します。
メッセージを表示する際、一時的にカーソルをオセロ盤下方に移動しますが、その後、カーソルを元の位置に復帰しなければなりません。
そこで必要になる処理が、カーソル位置のバックアップ/リストアです。
それぞれ、Push-Cursor関数、Pop-Cursor関数が担当します。
カーソル位置のバックアップ
170 : function Push-Cursor() 171 : { 172 : $script:savedCursorPositionX = $rui.CursorPosition.X 173 : $script:savedCursorPositionY = $rui.CursorPosition.Y 174 : }
カーソル位置のリストア
176 : function Pop-Cursor() 177 : { 178 : Move-Cursor $script:savedCursorPositionX $script:savedCursorPositionY 179 : }
2008-05-27
■[PowerShell][V2.CTP2]オセロゲームを作ろう(6):盤面描画とカーソル移動(1)
盤面描画とカーソル移動
今回はオセロの盤面描画とカーソル移動です。
以下のサンプルを実行してみてください。
1 : ############################################################################### 2 : # 初期処理/定義 3 : ############################################################################### 4 : # アセンブリ読み込み/参照 5 : $rui = $host.UI.RawUI 6 : [void][Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms") 7 : $keyMap = [Windows.Forms.Keys] 8 : # 初期化/定義 9 : $boardStartLine = 3 10 : ($startX, $startY) = (2, ($boardStartLine + 1)) 11 : ($msgStartLine, $debugMsgStartLine) = (21, 23) 12 : $script:screen = $null 13 : #色定義 14 : ($boardFgColor, $boardBgColor) = ("Black", "White") 15 : 16 : ############################################################################### 17 : # メイン 18 : ############################################################################### 19 : function Start-Game() 20 : { 21 : Backup-ScrBuf 22 : Draw-Init 23 : Get-InputKey 24 : Restore-ScrBuf 25 : } 26 : 27 : ############################################################################### 28 : # キー入力受付関連の関数 29 : ############################################################################### 30 : 31 : # キー入力関数 32 : function Get-InputKey() 33 : { 34 : while($true) 35 : { 36 : # キー取得 37 : $keyInfo = $rui.ReadKey("NoEcho,IncludeKeyDown") 38 : $keyCode = $keyInfo.VirtualKeyCode 39 : 40 : # Enterを入力 41 : if($keyCode -eq $keyMap::Enter){continue} 42 : 43 : # Escを入力→終了 44 : if($keyCode -eq $keyMap::Escape){return} 45 : 46 : # 上下左右のキー操作 47 : if($keyCode -eq $keyMap::Up) {Move-CursorByDirection ([String]$keyMap::Up); continue} 48 : if($keyCode -eq $keyMap::Down) {Move-CursorByDirection ([String]$keyMap::Down); continue} 49 : if($keyCode -eq $keyMap::Left) {Move-CursorByDirection ([String]$keyMap::Left); continue} 50 : if($keyCode -eq $keyMap::Right){Move-CursorByDirection ([String]$keyMap::Right);continue} 51 : 52 : # Aを入力→現在の座標を表示 53 : if($keyCode -eq $keyMap::A) 54 : { 55 : $dX = $rui.CursorPosition.X 56 : $dY = $rui.CursorPosition.Y 57 : $dMsg = "X : " + $dX + " Y : " + $dY 58 : Show-DebugMsg $dMsg 59 : } 60 : 61 : } 62 : } 63 : 64 : ############################################################################### 65 : # メッセージ表示関数 66 : ############################################################################### 67 : 68 : # メッセージ表示 69 : function Show-Msg($msg) 70 : { 71 : Show-MsgInner $msgStartLine $msg $msgFgColor $msgBgColor 72 : } 73 : 74 : function Show-DebugMsg($msg) 75 : { 76 : Show-MsgInner $debugMsgStartLine $msg $boardFgColor $boardBgColor 77 : } 78 : 79 : function Show-MsgInner([int]$pos, [String]$msg, [ConsoleColor]$fg, [ConsoleColor]$bg) 80 : { 81 : Push-Cursor 82 : Move-Cursor 0 $pos 83 : Write-Host -NoNewLine " " 84 : Move-Cursor 0 $pos 85 : Write-Host -NoNewLine $msg -ForegroundColor $fg -BackgroundColor $bg 86 : Pop-Cursor 87 : } 88 : 89 : ############################################################################### 90 : # スクリーン操作関数 91 : ############################################################################### 92 : 93 : # スクリーンバッファのバックアップ 94 : function Backup-ScrBuf() 95 : { 96 : $rect = New-Object System.Management.Automation.Host.Rectangle 97 : $rect.Left = 0 98 : $rect.Top = 0 99 : $rect.Right = $rui.WindowSize.Width 100 : $rect.Bottom = $rui.CursorPosition.Y 101 : $script:screen = $rui.GetBufferContents($rect) 102 : } 103 : 104 : # スクリーンバッファのリストア 105 : function Restore-ScrBuf() 106 : { 107 : Clear-Host 108 : $origin = New-Object System.Management.Automation.Host.Coordinates(0, 0) 109 : $rui.SetBufferContents($origin, $script:screen) 110 : $pos = New-Object System.Management.Automation.Host.Coordinates(0, $script:screen.GetUpperBound(0)) 111 : $rui.CursorPosition = $pos 112 : } 113 : 114 : ############################################################################### 115 : # 描画関連の関数 116 : ############################################################################### 117 : 118 : # 初期描画 119 : function Draw-Init 120 : { 121 : # タイトルの描画 122 : Clear-Host 123 : Write-Host "==================================" 124 : Write-Host " Othello by PowerShell " 125 : Write-Host "==================================" 126 : 127 : # オセロ盤の描画 128 : Move-Cursor 0 $boardStartLine 129 : Draw-Board 130 : 131 : # カーソル位置の初期化 132 : Move-Cursor $startX $startY 133 : } 134 : 135 : # オセロ盤の描画 136 : function Draw-Board() 137 : { 138 : Write-Host "┌─┬─┬─┬─┬─┬─┬─┬─┐" -ForegroundColor $boardFgColor -BackgroundColor $boardBgColor 139 : for($i=1; $i -le 7;$i++) 140 : { 141 : Write-Host "│ │ │ │ │ │ │ │ │" -ForegroundColor $boardFgColor -BackgroundColor $boardBgColor 142 : Write-Host "├─┼─┼─┼─┼─┼─┼─┼─┤" -ForegroundColor $boardFgColor -BackgroundColor $boardBgColor 143 : } 144 : Write-Host "│ │ │ │ │ │ │ │ │" -ForegroundColor $boardFgColor -BackgroundColor $boardBgColor 145 : Write-Host "└─┴─┴─┴─┴─┴─┴─┴─┘" -ForegroundColor $boardFgColor -BackgroundColor $boardBgColor 146 : } 147 : 148 : ############################################################################### 149 : # カーソル操作関数 150 : ############################################################################### 151 : 152 : # 指定した位置にカーソルを移動する 153 : function Move-Cursor([int]$newX, [int]$newY) 154 : { 155 : $coordinate = New-Object System.Management.Automation.Host.Coordinates $newX, $newY 156 : $rui.CursorPosition = $coordinate 157 : } 158 : 159 : # 上下左右のキーを押した時のカーソル移動 160 : function Move-CursorByDirection([String]$key) 161 : { 162 : ($oldX, $oldY) = ($rui.CursorPosition.X, $rui.CursorPosition.Y) 163 : if($key -eq "Up") {$newX = $oldX; if($oldY -gt 0){$newY = $oldY - 1}} 164 : elseif($key -eq "Down") {$newX = $oldX; $newY = $oldY + 1} 165 : elseif($key -eq "Left") {if($oldX -gt 0){$newX = $oldX - 1}; $newY = $oldY} 166 : elseif($key -eq "Right"){$newX = $oldX + 1; $newY = $oldY} 167 : Move-Cursor $newX $newY 168 : } 169 : 170 : function Push-Cursor() 171 : { 172 : $script:savedCursorPositionX = $rui.CursorPosition.X 173 : $script:savedCursorPositionY = $rui.CursorPosition.Y 174 : } 175 : 176 : function Pop-Cursor() 177 : { 178 : Move-Cursor $script:savedCursorPositionX $script:savedCursorPositionY 179 : } 180 : 181 : # 処理スタート 182 : Start-Game
上記コードを実行すると、オセロの盤面を描画します。
実行直後
上下左右キーを押下すると、キーの方向にカーソルが「1」移動します。
また、Aキーを押下すると現在のカーソル位置を表示します。
#カーソル位置を表示する処理はデバッグ目的で実装しています。
下に移動+カーソル位置表示
起動後、下キーを押下し、下方向に1移動。
その後、Aキーを押下し、カーソル位置を表示しました。
ただ、このままではオセロ盤の枠上にもカーソルが移動してしまいます。
このオセロゲームを、
- 上下左右キーでカーソル移動
- Enterで駒を置く
という仕様にする場合、カーソルが枠上に移動可能なのは好ましくありません。
駒が配置可能な8×8のマス上にカーソル移動を制限する必要があります。
つづく。。。
2008-05-15
■[PowerShell][V2.CTP2]オセロゲームを作ろう(5):スクリーンバッファ操作(3)
スクリーンバッファの操作
前回に引き続き、前々回紹介した、スクリーンバッファ操作のサンプルについて解説します。
スクリーンバッファのリストア
# スクリーンバッファのリストア function Restore-ScrBuf() { Clear-Host $origin = New-Object System.Management.Automation.Host.Coordinates(0, 0) $rui.SetBufferContents($origin, $script:screen) $pos = New-Object System.Management.Automation.Host.Coordinates(0, $script:screen.GetUpperBound(0)) $rui.CursorPosition = $pos }
Restore-ScrBuf関数は、Backup-ScrBuf関数でバックアップしたスクリーンバッファをリストアします。
手順は以下です。
- コンソールのクリア(Clear-Host)
- スクリーンバッファの描画座標の生成(Coordinatesオブジェクト)
- スクリーンバッファの描画(SetBufferContentsメソッド)
- カーソル位置を描画領域の最終行に移動(CursorPositionプロパティ)
まず、Clear-Host関数でコンソールをクリアします。
描画座標はコンソール左上ですので、X=0,Y=0のCoordinatesオブジェクトを生成します。
次に、SetBufferContentsメソッドでスクリーンバッファを描画します。
SetBufferContentsメソッド
System.Void SetBufferContents(Coordinates origin, BufferCell[,] contents) System.Void SetBufferContents(Rectangle r, BufferCell fill)
SetBufferContentsメソッドは2種類ありますが、今回利用するのは前者で、
引数は、描画座標(Coordinatesオブジェクト)とスクリーンバッファ(BufferCellオブジェクトの2次元配列)です。
最後に、カーソル位置を描画領域の最終行に移動します。
最終行は、リストアするスクリーンバッファの1次元目の最終配列要素(最終行)になります。
つづく。。。





