VBAを用いたレガシーシステムとTableauの連携
久しぶりのnote更新。レガシーシステムからデータ抽出してTableauCloudにアップする直前までを自動化させた備忘録。
システム更新後はBIでTableau活用が正式決定しているので、趣味も半分くらい仕事にできそう?本来は情シスさんの領域かもしれませんが。VBAでもやれることはたくさんあるんです。
レガシーシステムでデータ抽出
Sendkyes
レガシーなので自動化向きではなく愚直にSendkyesを活用。WScript.ShellのSendKeysとWScript.ShellのSendKeysで挙動が少し異なるので実装は後者で行っています。
(↓神髄さんの解説記事)
DataObjectを用いたクリップボード活用 → 失敗
当初はExcelのセルをコピー、レガシーでCtrl+Vで実装していたのですが、アプリの切替でエラーになるのがやだなぁと、変数を最初に取得して、都度、クリップボードに直接値を送り込んでCtrl+Vだけやれないかを模索しました。
→結果、できませんでした(笑)
何故かクリップボードへのアクセス?接続?が数回に1回ほどエラーになる。ネットをググってみると同様のエラー報告が多数されており、回避策も色々載っていました。
コードが複雑になりそうなのでその回避策は諦め、当初の予定通り、Excelとレガシーの往復作業をすることにしました。
神髄さんの解説記事にある通りAppActivateをフル活用です。
WaitかSleepか
待機時間を作るとき、お手軽にWaitを使うか、時間指定が柔軟(ミリ秒単位指定)のSleepを使うか、好みが分かれそうですが、Sleepは事前設定がちょっとあるため最初に使うのはWait。
ただ、ある程度動くにようになって待機時間が長いストレス解消のためにギリギリの線を攻めようと思ったらやっぱりSleepの方が使い勝手は良い。今回は秒単位制御なので、専用Sub作ってN秒を毎回指定させています。
Sub N秒待機(n As Integer)
Dim buf As String
buf = "0:00:" & CStr(n)
Dim waitTime As Variant
waitTime = Now + TimeValue(buf)
Application.Wait waitTime
'---Sleepで待機する場合
'Sleep n * 1000
End Sub
Waitでミリ秒単位を指定する方法はこちら。
Application.Wait [Now()] + Time/ 86400000
動的待機時間の制御 → UIAutomation
レガシーあるあるかもしれませんが、ここでWinAPIのGetActiveWindowでキャプションが取れない問題に直面。アクティブウインドウの名称が取れない、同じキャプションで複数のウインドウがある、などなどのレガシーならではの課題があります。
先日の勉強会?でたーぼーさん(@fenblen_puyo)に教えてもらったUIAutomationの知見を使って無事に取得(するまで数時間)
Function アクティブウインドウ名を取得() As String
Dim uia As New CUIAutomation
Dim elem As IUIAutomationElement
Set elem = uia.GetFocusedElement
アクティブウインドウ名を取得 = elem.CurrentName
End Function
大きな流れとしては・・・
ウインドウハンドルが待機中に何段階変わるかを確認
1秒ループさせながらウインドウハンドルが変わる都度取得
目的の回数まで達したら待機終了(今回は4回)
待機完了時にアクティブになっているべきウインドウの判定
エラー処理
待機後のウインドウ判定しているのは、レガシー起動直後に生まれるウインドウと待機中のウインドウがキャプションなどなど全く同じため、です。いやはや。
Prep用データフォルダへコピー
無事にデータが出力されたら、Prepが参照しているフォルダにコピー。その際に拡張子変更も行います。
今回の自動化をするまでは案件ごとにbasファイル作って対象ファイルフォルダにコピーしていました。コピー元ファイル名は固定、コピー後名称はInputBoxで変数化。
今回は汎用性持たせるため、コピー後名称を引数としたSubを作成。案件数がそんなに多くない(2つ)なので、現状はハードコーディング。たくさん増えてきたらパラメータ化しないといけないなぁ。使っている機能はおなじみのFileSystemObjectです。
コマンドラインでhyper更新
ここは師匠(@t0tsukawa)にアドバイスもらいつつ、Tableauの公式確認しながらなんとか実装。
Sub Prepフロー実行(ByVal fl As String)
On Error Resume Next
Dim WshShell As Object
Dim command As String
' WScript.Shell オブジェクトを作成
Set WshShell = CreateObject("WScript.Shell")
Dim path As String
Dim bat As String
'Dim fl As String'---フローのフルパス
path = "\Program Files\Tableau\Tableau Prep Builder XXXX.X\scripts"
bat = "\tableau-prep-cli.bat" '--- -t"
Dim code As String
'---フォルダパスとフローパスは前後にダブルクオート、フローパスの前には半角スペース
code = """" & path & bat & """" & " -t" & " " & fl
command = "%ComSpec% /c " & code
' コマンドを実行
WshShell.Run command, 1, False ' 1: ウィンドウを通常表示, True: 同期的に実行
' オブジェクトの解放
Set WshShell = Nothing
End Sub
twbxを起動
これに関する記事を頑張って探していたらなんとまたもや師匠の解説記事に出会う。師匠のたどった道に踏み込みつつある・・・??記事発見と相談のご報告をして、久しぶりの師弟交流でした(笑)
結果としては・・・単にShellで1行でした(涙)
Sub Tableau起動(ByVal z実行ファイル As String)
Shell "cmd /c start " & z実行ファイル
End Sub
起動後にデータ抽出更新、Cloudへのパブリッシュは自動化対象外としてますが、うまくいけばここも自動化できるかな・・・夢は広がる。