TeraTermマクロでStack overflow発生

だいぶ前に作ったTeraTermマクロのUNIXサーバ運用作業自動化ツールでエラーが出たというので見に行くと、Stack overflowというマクロのエラーダイアログが。
要件的には、psコマンドで特定のJVMプロセス終了を持って次に進む、20回チェックして終わってないならダイアログを表示し、その前の処理に戻るという内容ですが、たまたま今回の作業中に長時間動作するプロセスが実行中となっており、psを11回実行したところで停止した状態になってました。
これまで何回も使われているマクロの自動化ツールですが、この事象は今回初めて。
ちょっとググるとすぐに下記が出たのでマクロスクリプトを見直したら、ループ中にgotoで飛ぶというダメな作りを痛感。。
https://osdn.jp/ticket/browse.php?group_id=1412&tid=8024
goto否定主義者では無いし、多重ループだと解りにくくなるので、素直にループの外にgotoを出して対応。

timeout=3

:BEFORE_PROC
~前処理の詳細は割愛~

ifdefined OUTPUTBUF
if result = 0 then
   strdim OUTPUTBUF 500
endif

CHK_COUNT_OVER = 20
CHK_COUNT_NUM = 0
:CHK_PROCESS_ASTART
OUTPUTCNT = 0

flushrecv
setsync 1
CMD = "ps -ef | grep -e JOB0010 -e JOB0020 -e JOB0030 -e JOB0040 -e JOB0050 -e JOB0060 "
strconcat CMD "-e JOB0070 -e JOB0080 | grep -v grep"
sendln CMD

recvln
while result = 1
   recvln
   if result = 0 then
      break
   endif
   OUTPUTBUF[OUTPUTCNT] = inputstr
   OUTPUTCNT = OUTPUTCNT + 1
endwhile
setsync 0

if OUTPUTCNT > 0 then
   CHKSTR = '/bin/java'
   for ii 0 OUTPUTCNT - 1
      strscan OUTPUTBUF[ii] CHKSTR
      if result > 0 then
         CHK_COUNT_NUM = CHK_COUNT_NUM + 1
         if CHK_COUNT_NUM >= CHK_COUNT_OVER then
            messagebox 'プロセスが実行中で作業を続行出来ません。'#13'状態を確認してくだい(OKで前処理から続行します)' OUTPUTBUF[ii]
            goto BEFORE_PROC
         else
            pause 3
            goto CHK_PROCESS_ASTART
         endif
      endif
   next
endif

上記の33行目以降を下記に変更

CHKSTATUS = 1

if OUTPUTCNT > 0 then
   CHKSTR = '/bin/java'
   for ii 0 OUTPUTCNT - 1
      strscan OUTPUTBUF[ii] CHKSTR
      if result > 0 then
         CHK_COUNT_NUM = CHK_COUNT_NUM + 1
         if CHK_COUNT_NUM >= CHK_COUNT_OVER then
            CHKSTATUS = 2
            messagebox 'プロセスが実行中で作業を続行出来ません。'#13'状態を確認してくだい(OKで前処理から続行します)' OUTPUTBUF[ii]
         else
            CHKSTATUS = 3
            pause 3
         endif
      endif
   next
endif

if CHKSTATUS = 2 then
   goto BEFORE_PROC
elseif CHKSTATUS = 3 then
   goto CHK_PROCESS_ASTART
endif

P-COMマクロでホスト作業を自動化

 ホスト(=メインフレーム)ってまだ結構使っているところが多いように思います。
筆者が知る限り(3270系しか知りませんが)、エミュレータにPersonal Communications(P-COM)かFalconのどちらかを使っていて、どちらもマクロ機能があり、主に画面操作を自動化することが出来ます。
で、ちょっと前に、とあるホストの運用作業を自動化したいという話があったので、P-COMのホスト・アクセス・クラス・ライブラリーを使ったVBスクリプトを作ってみました。P-COMのホスト・アクセス・クラス・ライブラリーについては下記が参考になります。
http://www-01.ibm.com/support/knowledgecenter/SSEQ5Y_5.9.0/com.ibm.pcomm.doc/books/html/host_access08.htm?lang=ja
基本的にP-COM起動やホスト接続、終了以外は、画面の座標軸(縦と横)に対して文字を入力したり、指定場所に表示されているのが特定の文字かをチェックするという繰り返しになると思いますので、VBSではそれらをサブルーチンにしてます。
また、スクリプトの引数として、ログインIDとパスワード及び、事前に用意が必要な接続プロファイル(拡張子がwsのやつ)を指定する形になってます。

'2秒*30でタイムアウト判定
Const DEF_MAXCOUNT = 30

Dim oConnMGR, oECLPS, oECLOIA
Dim oParam, HUserID, HPassWd, ProFile
Dim i, idx

Set oParam = WScript.Arguments

For idx = 0 To oParam.Count - 1 
   Select Case idx
   Case 0
      HUserID = UCase( oParam(idx) )
   Case 1
      HPassWd = UCase( oParam(idx) )
   Case 2
      ProFile = oParam(idx)
   End Select
Next
'''''引数エラー処理割愛

'PCOMオブジェクトの作成
Set oConnMGR = CreateObject("PCOMM.autECLConnMgr")
Set oECLPS = CreateObject("PCOMM.autECLPS")
Set oECLOIA = CreateObject("PCOMM.autECLOIA")

'接続開始
Host_Start

If Check_Host("DISP3270") = false Then
	WScript.Sleep(10000)
	If Check_Host("DISP3270") = false Then
		Host_End
		WScript.Quit(-11)
	End If
End If

'(13/01) ログイン [LOGON ユーザーID] + CTL
Host_Input 13, 1, "LOGON " & HUserID , "[enter]", 1

'(8/20) パスワード [      ] + CTL 2回
Host_Input 8, 20, HPassWd , "[enter]", 2

Dim iPutLine, InputCmd

'画面遷移チェック (4,59) ユーザーIDが表示されるのをチェック
If Host_OutputChk(4, 59, Len(HUserID), HUserID) = False Then
	'エラー
	Host_Input 0, 0, "", "[pf3]", 2
End If

・・・以下省略

'終了
'F3 2回で後処理へ戻る
Host_Input 0, 0, "", "[pf3]", 2

'(5/27) 印刷せずにデータセットを削除「2」を選択 [2] + CTL
Host_Input 4, 27, "2", "[enter]", 1

'(2/01) ログオフ [LOGOFF] + CTL
Host_Input 2, 1, "LOGOFF", "[enter]", 1

'切断
Host_End

'ここでスクリプト終了
WScript.Quit(0)

'以下サブルーチン
'1 ホスト接続開始
Sub Host_Start
	
	oConnMGR.autECLConnList.Refresh
	
	'事前に取得したコネクションの数だけループして接続を停止する
	For i = 1 To oConnMGR.autECLConnList.Count   
		oConnMGR.autECLConnList(i).StopCommunication()
		WScript.Sleep 5000
		oConnMGR.StopConnection oConnMGR.autECLConnList(i).Name, "saveprofile=no"
		WScript.Sleep 5000
	Next

	Dim strCmd
	strCmd = "profile=" & ProFile & " connname=A"
	oConnMGR.StartConnection strCmd 
End Sub

'2 ホスト接続終了
Sub Host_End
	
	oConnMGR.autECLConnList.Refresh
	If oConnMGR.autECLConnList.Count > 0 Then
		oConnMGR.StopConnection "A", "saveprofile=no"
		WScript.Sleep 3000
	End If
End Sub

'3 ホストセッションの取得(接続タイプ)
Function Check_Host(ConnType)

	For i = 0 To DEF_MAXCOUNT
		oConnMGR.autECLConnList.Refresh
		If oConnMGR.autECLConnList.Count > 0 Then
			Exit For
		End If
		WScript.Sleep 2000
	Next

	For i = 1 To oConnMGR.autECLConnList.Count   '取得したコネクションの数だけループ
	    If oConnMGR.autECLConnList(i).ConnType = ConnType Then
	        '該当セッションのハンドルからセッションオブジェクトをセット
	        oECLPS.SetConnectionByHandle (oConnMGR.autECLConnList(i).Handle)
	        oECLOIA.SetConnectionByHandle (oConnMGR.autECLConnList(i).Handle)
	        Exit For
	    End If
	Next
	Check_Host = True
	If i > oConnMGR.autECLConnList.Count Then
		Check_Host = False
	Else
		For i = 0 To DEF_MAXCOUNT
			WScript.Sleep 2000
			'処理完了待ち
			If oECLOIA.InputInhibited = 0 Then
				If Host_OutputChk(12, 16, 5, "*****") = True Then
					Exit For
				End If
			End If
		Next
		'起動確認タイムアウト
		If i >= DEF_MAXCOUNT Then
			Host_End
			WScript.Quit(-2)
		End If
	End If

End Function

'4 ホスト画面への入力(Y座標、X座標、文字列、キー入力、キー入力回数)
Sub Host_Input(argY, argX, argStr, argKeyStr, argKeyCnt)
	Dim iKey
	
	If argY <> 0 And argX <> 0 Then
		oECLPS.SetCursorPos argY, argX
		oECLPS.SendKeys argStr
	End If
	
	'エンターキー
	If argKeyStr <> "" Then
		For iKey = 1 To argKeyCnt
			oECLPS.SendKeys argKeyStr
			For i = 0 To DEF_MAXCOUNT * 10
				WScript.Sleep 200
				'処理完了待ち
				If oECLOIA.InputInhibited = 0 Then
					Exit For
				End If
			Next
			If i >= DEF_MAXCOUNT * 10 Then
				Host_End
				WScript.Quit(-2)
			End If
		Next
	End If

End Sub

'5 画面表示内容のチェック(Y座標、X座標、文字列の長さ、文字列 一致したらtrueを返す
Function Host_OutputChk(argY, argX, argLen, argStr)

	Dim strBuff
	
	strBuff = oECLPS.GetTextRect(argY, argX, argY, argX + argLen-1)
	If Trim(strBuff) = Trim(argStr) Then
		Host_OutputChk = True
	Else
		Host_OutputChk = False
	End If

End Function

やっかいなのは、あくまで画面操作なので接続とか応答に時間が掛かったり、いろいろ問題が出てきます。
ちょっと雑にSleepをガンガンかましていますが、お察しください。。

TeraTermマクロでプロセスをKILL

UNIXサーバ作業の自動化として、特定のプロセスをKILLするマクロを書きました。
前提として
・停止対象のプロセスはJavaの常駐JOBが3つ
・プロセス停止前にチェック処理が必要
このチェック処理が複雑なので、マクロにしました。
下記は、サーバにログオンされた状態以降の抜粋です。チェック処理と最後のログオフは省略してます。
実際のツールはHTAで作業メニュー画面があり、画面操作でマクロを起動する構成にしました。

strdim OUTPUTBUF 500

; KILLする対象のプロセスがPSコマンドで表示される名前
strdim CHK_PROCESS_NAMES 3
CHK_PROCESS_NAMES[0] = 'jp.aaa.bbb.jobA'
CHK_PROCESS_NAMES[1] = 'jp.aaa.bbb.jobB'
CHK_PROCESS_NAMES[2] = 'jp.aaa.bbb.jobC'

; プロセスIDをゲットできたかのフラグ
CHK_PROCESS_RES = 0

timeout = 5

for iii 0 2
   
   CHK_PROCESS_NAME = CHK_PROCESS_NAMES[iii]
   ;; プロセスID取得subコール
   call GET_PROCESSID
   if CHK_PROCESS_RES <> 1 then
      messagebox '停止対象のプロセスが起動していません。'#13'状態を確認してくだい(OKで次プロセスの停止に進みます)' CHK_PROCESS_NAME
   else
      strlen CHK_PROCESS_ID
      if result = 0 then
         messagebox '停止対象のプロセスIDが取得出来ませんでした。'#13'状態を確認してくだい(OKで続行します)' CHK_PROCESS_NAME
      else
         ;; KILL前のチェック処理(省略・・)

         ;; プロセスKILL
         CMD = 'kill -9 '
         strconcat CMD CHK_PROCESS_ID
         sendln CMD
         wait '#' '$'

         ;; プロセスID取得subコール(Killしたのに残っていたら警告)
         call GET_PROCESSID
         if CHK_PROCESS_RES <> 2 then
            messagebox 'プロセスが停止できていません。'#13'状態を確認してくだい(OKで続行します)' CHK_PROCESS_NAME
         endif
      endif
   endif
next

;;マクロ終了
end

;;; sub プロセス状態確認 start
:GET_PROCESSID
CHK_PROCESS_RES = 2
OUTPUTCNT = 0
flushrecv

setsync 1
CMD = 'ps -ef | grep '
strconcat CMD CHK_PROCESS_NAME
sendln CMD

recvln
while result = 1
   recvln
   if result = 0 then
      break
   endif
   OUTPUTBUF[OUTPUTCNT] = inputstr
   OUTPUTCNT = OUTPUTCNT + 1
endwhile
setsync 0

if OUTPUTCNT > 0 then
   ;; Javaプロセスか確認(Grepでない)
   CHKSTR = '/bin/java'
   for i 0 OUTPUTCNT - 1
      strscan OUTPUTBUF[i] CHKSTR
      if result > 0 then
         ;; プロセスIDセット
         strcopy OUTPUTBUF[i] 10 8 CHK_PROCESS_ID
         CHK_PROCESS_RES = 1
         break
      endif
   next
endif

return
;;; sub プロセス状態確認 end

TeraTermマクロのfileopenで失敗

去年作ったTeraTermマクロのサーバ作業の自動化ツールで1年以上問題無かった箇所でエラーになるとの事で見たら、端末のセキュリティレベルが変わっていてマクロのfileopenで失敗している事が判明。fileopenしているファイルが書き込めないように端末設定が変わっていたのが原因で、マクロのfileopenでは書き込めないとエラーになるんですね。
 で、マクロの仕様をネットで見たらfileopenの仕様に読み取り専用モードのオプションが4.85以降で追加されてるじゃないですか!
 かといって、ツールで使っているTeraTermマクロをバージョンアップするのも、20パターンくらいの作業を自動化しているのでちょっと躊躇。。
 作業自動化ツールをマクロで作ることにしたのは、基本ミドル・運用アプリ系の導入無し、複数サーバで同じような作業があるし、サーバ側に作業自動化シェルとか置くと、ここのサーバ運用的に変更の敷居が高いという環境上の要件からなんですが、マクロにしたらしたらで端末の設定変更でなくマクロを変えればいいじゃん的な発想に変わってしまうという、、まさにジレンマですね。変えやすいものは変われるけれど、それを維持するのは結構メンドクサイんです。。このメンドクサイという事実がシステムの開発・運用コストに直結しているハズなんですけどね。。

VBSでFTP

あるWindowsサーバのログファイルを別サーバへ定期的にFTP送信したいとの事で、VBSで簡単なスクリプトを作りました。これまでは、バッチやスクリプトでFTP用のコマンドファイルを作り、FTP -S:ファイル名で実行するような感じだったのですが、最近は、スクリプトだけでできるようです。
下記例は、D:\log\tempというディレクトリに一つのzipファイルが置かれており、そのファイルを送信するという前提です。
ちなみにzipファイル自体の名前でstrSrcを指定するとCopyHereが勝手にZipの中身を解凍した状態で送信されます。。そういや、CopyHereでzipの圧縮や解凍もできるからそういう事なんでしょうか。。恐るべしCopyHere

'FTP送信指定
Const HostName = "192.168.1.101"
Const UserID = "userid"
Const UserPass = "password"
Const DestDir ="Upload"

Dim strDest,strSrc, UpFileName, UpFileSize, oSH, objDest

strSrc="D:\log\temp"

strDest = "ftp://" & UserID & ":" & UserPass & "@" & HostName & "/" & DestDir

Set oSH = CreateObject("Shell.Application")
Set objDest = oSH.NameSpace(strDest)

WScript.Echo "[" & strSrc & "]FTP転送対象"

For Each objItem In oSH.NameSpace(strSrc).Items
WScript.Echo "[" & strSrc & "]FTP転送ファイル [" & objItem.Name & "]," & objItem.Size
	UpFileName = objItem.Name
	UpFileSize = objItem.Size
    '&H04 + &H10は効かない・・・
	objDest.CopyHere objItem, &H04 + &H10
Next
'非同期なのでスリープで待つ・・・
Wscript.Sleep 15000

'リモートディレクトリの送信ファイル存在確認
Dim CheckOK, iWaitCnt
CheckOK = False
For iWaitCnt = 0 To 10
Set objDest = oSH.NameSpace(strDest)
    For Each objItems In objDest.Items
		If UpFileName = objItems.Name Then
    		'リモートファイルのサイズは取れないみたい・・・
       		WScript.Echo "[" & strSrc & "]FTP転送ファイル確認 [" & objItems.Name & "]," & objItems.Size
	   		CheckOK = True
	   		Exit For
		End If
    Next
	If CheckOK = True Then
		Exit For
	End If
    Wscript.Sleep 10000
    WScript.Echo "FTP転送待ち[" & iWaitCnt & "]" 
Next
If CheckOK = False Then
	WScript.Echo "FTP転送に失敗しました " & Err.Number
Else
	WScript.Echo "FTP転送正常終了"
End If

でも、このCopyHere、FTPでは非同期のようで送信終了タイミングが解らなくスリープ待ちせざるを得なかったり、上書き確認のダイアログがどうしても消せない等、ちょっとクセが強いです。。
今回の要件的にはまあ上書きの対処は見切りました。確実性が必要な処理だとこのやり方はしないかな~