ExcelマクロでTeraTermを使ってルーターのコンフィグを設定する

##最初に
　前回もそうですが、今回もVBAです。
　Excel VBA の2回名です。Qiitaは開発者のサイトですから、この手の記事は参照する人は少ないですねー。　
　ルーターの設定を大量にする仕事があり、エクセルの一覧に基づいて提供されているコンフィグを設定するだけの作業だったのですが、大量にやらないといけないのでマクロ化しちゃいました。
　基本マクロは使い捨てと考えているのすが、また作るときの為に覚書として書いてます。この記事はほとんどTeraTermマクロの説明になります。

##使うもの
　EXCEL
　TeraTerm(マクロ実行モジュールを入れておくこと)

##どのような処理にするのか
　エクセルの内容からTeraTermのマクロファイルを作成してそれを実行させる。

##ブックの構成
　ここでは以下の構成でブックを作る
1.データシート
 　データシートはマクロの置き換え文字を設定したシート。応用する場合は業務で使うデータリストを利用する。
　![データシート.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/482873/712bfca9-d53b-f6e9-1a87-8a215419276d.png)

2.マクロシート
　TeraTermマクロの雛形を1列に書いたシート。別のテキストファイルにするとまとめて管理する必要があり面倒なのでシートーに直接書き込む。

3.設定シート
　TeraTermマクロ実行exeのパスとシリアルポートの番号等の共通設定を記述している。

##Excelマクロ
　データシートの先頭にボタンを作ってマクロを以下のマクロを設定する。
　設定したマクロの中身は以下の通りです。

```
Option Explicit

'================================================================================
'   Win32 API 関数の宣言
'================================================================================
Declare Function WaitForSingleObject Lib "kernel32" (ByVal hHandle As Long, _
                                     ByVal dwMilliseconds As Long) As Long
Declare Function OpenProcess Lib "kernel32" (ByVal dwDesiredAccess As Long, _
                                             ByVal bInheritHandle As Long, _
                                             ByVal dwProcessId As Long) As Long
Declare Function CloseHandle Lib "kernel32" (ByVal hObject As Long) As Long

'================================================================================
'   定数
'================================================================================
Public Const PROCESS_ALL_ACCESS As Long = &H1F0FFF
Public Const INFINITE As Long = &HFFFF

Public Const CONST_データ名称行 As Integer = 2
Public Const CONST_データ開始行 As Integer = 3
Public Const CONST_置換文字ヘッダ As String = "##"
Public Const CONST_シリアルポート置換文字 As String = "##SerialPort"
Public Const CONST_マクロファイル名 As String = "t_macro.ttl"

'--------------------------------------------------------------------------------
'   マクロ実行ボタンの実行エントリー
'--------------------------------------------------------------------------------
Sub マクロ実行_Click()

    Dim 置換データ() As String
    Dim 設定 As cls設定
    Dim マクロファイルパス As String
    
    ' 現在選択されている行のデータを取得する
    If Get選択データ(置換データ) = False Then Exit Sub
    
    ' 実行確認
    If MsgBox(ActiveCell.row & "行目のデータでマクロを実行しますか？", vbYesNo, "確認") = vbNo Then Exit Sub
    
    ' 設定の取得
    Set 設定 = Get設定()

    ' TeraTermマクロの作成
    マクロファイルパス = CreateTeraTermマクロ(設定, 置換データ)
    
    ' TeraTermマクロの実行
    DoTeraTermマクロ 設定, マクロファイルパス
    
    ' マクロファイルの削除
    If Dir(マクロファイルパス) <> "" Then
        Kill マクロファイルパス
    End If
    
End Sub

'--------------------------------------------------------------------------------
'   選択行の置換情報を取得する（複数行選択未対応）
'   【引数】
'       置換データ      【返値】選択した行のデータと置換文字（タイトル行の値の先頭に置換文字ヘッダを追加したもの）の配列を返す
'   【返値】
'       true    正常に取得できた
'       false   データ行以外の行が選択されていた
'--------------------------------------------------------------------------------
Function Get選択データ(ByRef 置換データ() As String) As Boolean

    Dim データ終了行 As Integer
    Dim データ終了列 As Integer
    Dim 列index As Integer
    
    With ActiveSheet
    
        データ終了行 = .Cells(10000, 1).End(xlUp).row
        
        ' データ範囲外の場合は処理終了
        If (ActiveCell.row >= CONST_データ開始行) And (ActiveCell.row <= データ終了行) Then
        
            データ終了列 = .Cells(CONST_データ名称行, 1000).End(xlToLeft).Column
            
            ' 置換データの取得
            ReDim 置換データ(データ終了列 - 1, 1)
            For 列index = 1 To データ終了列
                置換データ(列index - 1, 0) = CONST_置換文字ヘッダ & Trim(.Cells(CONST_データ名称行, 列index).Text)
                置換データ(列index - 1, 1) = Trim(.Cells(ActiveCell.row, 列index).Text)
            Next 列index
            
            Get選択データ = True
            
        Else
        
            MsgBox "データとして有効な行が選択されていません。"
            Get選択データ = False
        End If
    
    End With
    
End Function

'--------------------------------------------------------------------------------
'   全体の設定の取得。ここではTeraTermマクロの実行パスしか取得していないが、共通の設定があれば追加する
'   【返値】
'       設定情報クラスのインスタンス
'--------------------------------------------------------------------------------
Function Get設定() As cls設定

    Dim 設定 As cls設定
    
    Set 設定 = New cls設定
    
    With ThisWorkbook.Worksheets("設定")
        設定.TeraTermマクロ実行パス = .Range("B1").Text
        設定.シリアルポート番号 = .Range("B2").Text
    End With
    
    Set Get設定 = 設定
    
End Function

'--------------------------------------------------------------------------------
'   TeraTermマクロで実行するマクロを記述したファイルを作成し、そのパスを返す
'   【返値】
'       TeraTermマクロで実行するマクロを記述したファイルのパス
'--------------------------------------------------------------------------------
Function CreateTeraTermマクロ(設定 As cls設定, 置換データ() As String) As String

    Dim マクロシート As Worksheet
    Dim マクロ最終行 As Integer
    Dim マクロファイルパス As String
    Dim マクロファイル As Integer
    Dim マクロ行 As Integer
    Dim 行データ As String
    Dim 置換文字index As Integer
    
    ' マクロシートの取得
    Set マクロシート = ThisWorkbook.Worksheets("マクロ")
    
    ' マクロ最終行の取得
    マクロ最終行 = マクロシート.Cells(10000, 1).End(xlUp).row
    
    ' 出力するマクロファイルのオープン
    マクロファイルパス = ThisWorkbook.Path & "\" & CONST_マクロファイル名
    マクロファイル = FreeFile
    Open マクロファイルパス For Output As #マクロファイル
    
    ' マクロシートの全ての行を書き込む
    For マクロ行 = 1 To マクロ最終行
    
        ' マクロシートの１行を取得
        行データ = マクロシート.Cells(マクロ行, 1).Value
        
        ' 「##」が入っている場合は置換処理を行う
        If InStr(行データ, "##") > 0 Then
        
            ' シリアルポート置換文字
            行データ = Replace(行データ, CONST_シリアルポート置換文字, 設定.シリアルポート番号)

            ' 全ての置換文字を実行
            For 置換文字index = 0 To UBound(置換データ)
            
                行データ = Replace(行データ, 置換データ(置換文字index, 0), 置換データ(置換文字index, 1))
            
            Next 置換文字index
            
        End If
    
        ' 1行をマクロファイルに書き込む
        Print #マクロファイル, 行データ
    
    Next マクロ行

    ' マクロファイルを閉じる
    Close #マクロファイル
    
    ' マクロファイルのパスを返す
    CreateTeraTermマクロ = マクロファイルパス
    
End Function

'--------------------------------------------------------------------------------
'   TeraTermマクロで実行するマクロを記述したファイルを作成し、そのパスを返す
'   【返値】
'       TeraTermマクロで実行するマクロを記述したファイルのパス
'--------------------------------------------------------------------------------
Sub DoTeraTermマクロ(設定 As cls設定, マクロファイルパス As String)

    Dim タスクID As Long  ' 実行タスクID
    Dim プロセスID As Long   ' プロセスID
    
    ' マクロの実行
    タスクID = Shell("""" & 設定.TeraTermマクロ実行パス & """ """ & マクロファイルパス & """", vbMinimizedFocus)
        
    ' プロセスハンドルの取得
    プロセスID = OpenProcess(PROCESS_ALL_ACCESS, 0, タスクID)
    
    ' プロセスハンドルが返されたかを判定
    If プロセスID <> 0 Then
        ' プロセスのシグナル待ち
        Call WaitForSingleObject(プロセスID, INFINITE)
        ' プロセスクローズ
        CloseHandle プロセスID
    End If
    
End Sub
```
　
　ここでしているのは以下の操作です。
1.「マクロ」シートの内容を「データ」シートの内容で一部置換したマクロファイルを作成
2.作成したマクロファイルを利用するTeraTermマクロ実行プロセスを実行しプロセスの終了を待つ。

　プロセスの扱いの部分だけちょっと複雑そうに見えますが、WindowsAPIを利用しただけです。
　応用すれば自身の管理用のエクセルファイルに組み込んで使ってもらえると思います。

##TeraTermマクロのサンプル
　簡単なTeraTermマクロのサンプルと、その説明です。応用すればTeraTermを利用したいろいろな処理を自動実行する仕組みが出来上がります。

```
connect '/C=##Serialport'

logopen '##logpath' 0 0

yesnobox 'ルーターとの接続を確認し、ルーターの電源を入れてください。' '確認'
if result = 0 then
  end
endif

pause 1

timeout = 600
wait 'initial configuration dialog?'
errMsg = '起動後のメッセージに予定の文字列が見つかりません（「initial configuration dialog?」）。'
call subTimeoutErrorCheck

timeout = 30
sendln 'n'
wait 'autoinstall'
sendln 'n'

timeout = 60
wait 'Press RETURN'
errMsg = '初期化ダイアログの表示否定失敗（>プロンプトが返ってこない）。'
call subTimeoutErrorCheck

timeout = 10

sendln
wait '>'
errMsg = '初期エンターで想定外（>プロンプトが表示されない）。'
call subTimeoutErrorCheck

sendln 'enable'
wait '#'
errMsg = 'enableに失敗しました（#プロンプトが表示されない）。'
call subTimeoutErrorCheck
pause 1

sendln 'ter len 0'
wait '#'
pause 1

sendln 'configure terminal'
wait 'Router(config)#'
errMsg = 'configure terminalに失敗しました（Router(config)#プロンプトが表示されない）。'
call subTimeoutErrorCheck

timeout = 3
fileopen FH '##configpath' 0

while 1
  filereadln FH line
  if result=1 then 
    break
  endif

  sendln line
  wait '(config' '##enablePrompt' 'ACCEPT? (yes/[no]):'
  if result == 3 then
    sendln 'yes'
    wait '(config' '##enablePrompt'
  endif
endwhile
fileclose FH

flushrecv

timeout = 10

sendln 'write memory'
wait '#'
errMsg = 'コンフィグを保存できませんでした。（#が表示されない）'
call subTimeoutErrorCheck

logclose

disconnect
closett
end

:subTimeoutErrorCheck
if result == 0 then
 messagebox errMsg 'エラー'
  logclose
  end
endif
return

```
　##で始まる文字列は「データ」シートでタイトルが一致するものの選択している値と置き換わるようにエクセルのマクロで設定してます（##Serialportだけちょっと特殊です。あと、サンプル画面ではタイトルは「パラメータ１〜１０」となっているので変更する必要があります。）。

　では、TeraTermマクロの簡単な説明。

```
connect '/C=##Serialport'
```
　TeraTermをシリアルポートでオープンしてます。ポート番号は設定シートの内容に置き換えられるように「##Serialport」にしてあります。

```
logopen '##logpath' 0 0
```
　ログファイルをオープン。データシート項目「logpath」にログを出力するパスを設定しておく必要があります。業務ではよくログの取得を忘れてしますのですが、これを利用すれば忘れることはないですね。（出力パスのエラーチェックはしてません。必要ならTeraTermマクロを調べてください。）

```
yesnobox 'ルーターとの接続を確認し、ルーターの電源を入れてください。' '確認'
if result = 0 then
　end
endif
```
　ダイアログメッセージを表示してます。（このマクロはルーターのコンフィグを設定するためのマクロで、ログ取得をルーターの起動から行わせるためにここでルーターの起動を促しています。）メッセージボックスの選択結果は「result」に入ってきます。TeraTermマクロでは全ての変数は宣言不要で型は無しのグローバル変数です。この「result」の様にマクロで自動的に設定される変数もあります。ここでは「0」が「No」を選んだ場合です。Noを選んだ場合「end」コマンドでマクロを終了させます。

```
pause 1
```
　1秒待機です。待機時間は秒指定です。

```
timeout = 600
```
　その後のコマンドでの待機時間を設定します。600秒ですので10分ですね。ルーターの起動完了を待つためにこの時間にしています。

```
wait 'initial configuration dialog?'
errMsg = '起動後のメッセージに予定の文字列が見つかりません（「initial configuration dialog?」）。'
call subTimeoutErrorCheck
```
　「wait」指定した文字列が表示されるのを待つという意味で、この時のタイムアウトが先に指定した600秒になるわけです。「wait」の後に「call subTimeoutErrorCheck」でエラーチェックのサブルーチン（マクロの最後に記述してます）を利用しています。エラー（result=0）だったら変数「errMsg」のメッセージを出して終了するようにしているので、先にエラーメッセージを設定しています。今どきの開発言語とはちがいサブルーチンはこのように、ラベル（:subTimeoutErrorCheck）でジャンプして、「return」で元に戻るようになっています。エラーの場合は「end」で終わっちゃうので戻れませんが。関数なんてものは定義できないみたいです。ちなみにラベルの前に「end」を入れておかないと、ラベルを呼び出さなくてもマクロが最後にこの部分を実行しちゃいます。ちなみに「'initial configuration dialog?'」を待っているのはとあるルーターの初期起動の完了時にこのメッセージが表示されるからです。

```
timeout = 30
sendln 'n'
```
　この時点でルーターの起動が終わっているので、タイムアウトを30秒にして「sendln 'n'」で「n」の入力と改行を送信します。sendlineは1行送信です。

基本的にはこの後、「wait」と「sendln」を利用してターミナルの処理を実行していき、

```
logclose

disconnect
closett
end
```
　「logclose」でログを閉じ
　「disconnect」でシリアルポートの接続を閉じ
　「closett」でTeraTermを終了し
　「end」でマクロを終了します。

```
timeout = 3
fileopen FH '##configpath' 0

while 1
  filereadln FH line
  if result=1 then 
    break
  endif

  sendln line
  wait '(config' '##enablePrompt' 'ACCEPT? (yes/[no]):'
  if result == 3 then
    sendln 'yes'
    wait '(config' '##enablePrompt'
  endif
endwhile
fileclose FH

flushrecv
```
　少し戻りますが上記の部分のマクロではそれ以外に外部ファイルの取り込みをしています。外部ファイルの内容を1行ずつ実行させています。この中での「wait」は待つ文字列を3つ指定してます。どれが出てきたかが「result」に入っていて、0ならタイムアウトです。ちなみに、この例では3の時に「yes」を送信していますが、これは設定中にライセンスの確認処理が発生することがあり、その時に先に進むようにしています。
　ファイル取込み後に「flushrecv」で受診バッファのクリアをしています。受診バッファはマクロ実行中であろうがずっと出力をためています。「wait」を実行すると、そこまでの受信データは削除されるのですが、時折バッファ内にその後に待つべき文字列が残っていると反応してしまうので、ファイル取込み後、ファイル処理の影響をなくすために、ここで一旦バッファを削除しています。
　
　まあ、こんな感じでTeraTermマクロを記述していくと、エクセルからTeraTermを実行していろいろなことができるようになり、作業がはかどると思います。
　あとは、VBSにして配布することで現地作業を簡易化するなどの方法もあります。

　長々と書きましたが、だれかの作業の助けになれば幸いです。