【WinActor】シナリオ起動VBSを書いてみた

# 経緯

先日、こちらに登壇させていただきました。

https://rpacommunity.connpass.com/event/271850/

実際に現場で運用しているシナリオ起動VBSについてのお話をさせていただきました。
LTの都合上、どんなコードかといった詳しいことはご紹介できなかったため、補足としてこちらの記事でコードを公開します。

## 前提

| 前提情報 | 内容 |
|:--|:--|
|動作確認|WinActor v.6.3.2／Windows10|
|シナリオ格納先|c:\RPA|
|シナリオファイル名|担当【管理部・第1課】管理システムデータ印刷シナリオ_ver01_01_20221227.ums6|
|更新時に変える部分|verXX_YY_YYYYMMDD|
|担当ごとに変える部分|担当【AA部・BB課】|
|変動しない（ベース）部分|管理システムデータ印刷シナリオ|
|その他|デスクトップに「WinActor.exe - ショートカット」が生成されている|

![1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/416977/ef944551-effe-3098-7484-e149f6159206.png)
また、私のところではシナリオ内で状況に合わせてWinActor終了ノードを配置するようにしているため、起動オプションにWinActor終了は組み込んでいません。
お手元のシナリオ実行環境に合わせて修正してください。

## シナリオ起動ショートカットの作成の仕方

そもそもシナリオ起動ショートカットって何？というときは、こちらのサイトが画像付きで分かりやすかったです。

https://www.knet-bpr.com/column/winactor-lectureship/20210412.html


## コードパターン１：シナリオの格納先がほぼ固定

<dl>
  <dt>説明</dt>
  <dd>VBSファイルの中で直接シナリオファイルの格納先を指定しておきます。</dd>
  <dd>そのフォルダの中を検索し、シナリオのベース名が含まれるシナリオファイルを起動します。</dd>
  <dd>このVBSファイルを実行端末のデスクトップ等に置いておけば、誰でも簡単にシナリオを起動・実行させることができます。</dd>
  <dd>　</dd>
  <dt>活用のポイント</dt>
  <dd>
 
```vb
Public Const SCENARIO_FOLDER_PATH = "C:\RPA"
``` 

`"C:\RPA"` がシナリオの置いてあるフォルダのパスです。
ここを書き換えることで、その端末でアクセス可能なフォルダならどこでも自動実行対象にできます。
  </dd>
  <dd>
 
```vb
Public Const SCENARIO_FILE_FILTER ="*管理システムデータ印刷シナリオ*.ums6"
``` 

`"*管理システムデータ印刷シナリオ*.ums6"` がシナリオファイルを検索するためのキーワードです。
ワイルドカード（*や?）が使えるので、ファイル名の一部が変化しても柔軟に対応できます。
ライブラリ「<strong>ファイル検索</strong>」と同じフィルタ方法なので、使ったことがあれば同じ感覚で設定することができます。
　
  </dd>
  <dt>おすすめ点</dt>
  <dd>VBSファイルを好きなところに置いてどこからでも実行できるため、便利です。</dd>
  <dd>シナリオファイルを格納している場所や、シナリオ名のベース部分が変わらない限り、修正の必要はありません。</dd>
  <dd>　</dd>
  <dt>気になる点</dt>
  <dd>格納場所等が変わった際には、VBSを直接開き、格納先のパスの修正をする必要があります。</dd>
  <dd>VBSファイルを開くのは怖い、よく分からないのでできないという場合も少なくなく、作成者の対応が必要な場合があります。</dd>
  <dd>　</dd>
</dl>

```vb:シナリオ起動_格納先指定版.vbs
Option Explicit
'***********************************************************
'シナリオ情報（基本的にここだけ更新すればOK）
Public Const SCENARIO_FOLDER_PATH = "C:\RPA"
Public Const SCENARIO_FILE_FILTER ="*管理システムデータ印刷シナリオ*.ums6"
'***********************************************************
'デスクトップに生成されるWinActor.exeのショートカットの名前
Public Const WINACTOR_SHORTCUT_NAME = "WinActor.exe - ショートカット"

'開始
Call start_winactor_scenario

'============================================
'WinActorシナリオを実行
'============================================
Private Sub start_winactor_scenario()
    
    Dim objWSH: Set objWSH = CreateObject("WScript.Shell")
    Dim strFileFilter
    Dim strScenarioFilePath
    Dim strWinActorFilePath
    Dim strWinActorShortcutPath
    Dim objWinActorShortcut
    Dim strCmd
    
    '実行するシナリオファイルを取得 /キーワードを含む拡張子ums+1桁の数字となるファイル
    strScenarioFilePath = search_target_file(SCENARIO_FOLDER_PATH, SCENARIO_FILE_FILTER)
    If strScenarioFilePath = "" Then
        WScript.Echo "シナリオファイルが見つかりません。" _
                        & vbCrLf & "----------------------" _
                        & vbCrLf & "＜検索対象フォルダ＞" & vbCrLf & SCENARIO_FOLDER_PATH _
                        & vbCrLf & "＜検索キーワード＞" & vbCrLf & SCENARIO_FILE_FILTER & "（拡張子：ums）"
        WScript.Quit
    End If
    
    'デスクトップにあるWinActor.exeのショートカットを取得 / WinActor.exe - ショートカット.lnk
    strFileFilter = WINACTOR_SHORTCUT_NAME
    If LCase(Right(strFileFilter, 4)) <> ".lnk" Then
        strFileFilter = strFileFilter & ".lnk"
    End If
    strWinActorShortcutPath = search_target_file(objWSH.SpecialFolders("desktop"), strFileFilter)
    'パブリックデスクトップに置いてあるパターンもあるため再検索
    If strWinActorShortcutPath = "" Then
        strWinActorShortcutPath = search_target_file("C:\Users\Public\Desktop", strFileFilter)
    End If
    If strWinActorShortcutPath = "" Then
        WScript.Echo WINACTOR_SHORTCUT_NAME & "のショートカットがデスクトップに生成されていません。"
        WScript.Quit
    End If
    
    'ショートカットのリンク先（WinActor.exe）取得
    Set objWinActorShortcut = objWSH.CreateShortcut(strWinActorShortcutPath)
    strWinActorFilePath = objWinActorShortcut.TargetPath
    
    'コマンドライン作成/"WinActor.exeパス" -f "実行するシナリオファイルパス" -r
    strCmd = ""
    strCmd = strCmd & Chr(34) & strWinActorFilePath & Chr(34)
    strCmd = strCmd & " -f " & Chr(34) & strScenarioFilePath & Chr(34)
    strCmd = strCmd & " -r"
    
    'シナリオ起動
'    MsgBox strCmd
    objWSH.Run strCmd

End Sub

'============================================
'検索フォルダ内で、指定キーワードと指定拡張子が含まれるファイルを取得
'============================================
Private Function search_target_file(strSearchFolderPath, strFilter)
    Dim objFSO: Set objFSO = CreateObject("Scripting.FileSystemObject")
    Dim objRE: Set objRE = CreateObject("VBScript.RegExp")
    Dim f
    If Not objFSO.FolderExists(strSearchFolderPath) Then
        WScript.Echo "指定された検索フォルダが見つかりません。" _
                        & vbCrLf & "＜検索対象フォルダ＞" & vbCrLf & strSearchFolderPath
        WScript.Quit
    End If
    'ワイルドカード変換/ライブラリ「ファイル検索」のフィルタの調整と同じ
    strFilter = Replace(strFilter, "\", "\\")
    strFilter = Replace(strFilter, "+", "\+")
    strFilter = Replace(strFilter, ".", "\.")
    strFilter = Replace(strFilter, "|", "\|")
    strFilter = Replace(strFilter, "{", "\{")
    strFilter = Replace(strFilter, "}", "\}")
    strFilter = Replace(strFilter, "[", "\[")
    strFilter = Replace(strFilter, "]", "\]")
    strFilter = Replace(strFilter, "(", "\(")
    strFilter = Replace(strFilter, ")", "\)")
    strFilter = Replace(strFilter, "$", "\$")
    strFilter = Replace(strFilter, "^", "\^")
    strFilter = Replace(strFilter, "?", ".")
    strFilter = Replace(strFilter, "*", ".*")
    '正規表現パターン登録
    objRE.Pattern = "^" & strFilter & "$"
    For Each f In objFSO.GetFolder(strSearchFolderPath).Files
        If objRE.Test(f.Name) Then
            search_target_file = f.Path
            Exit For
        End If
    Next
End Function

```

## コードパターン２：シナリオ格納場所を移動したり、横展開したりする


<dl>
  <dt>説明</dt>
  <dd>ほとんどパターン１一緒ですが、シナリオの格納先をVBSの中で自分の居る場所を読み取ります。</dd>
  <dd>このVBSファイルをシナリオファイルと同じ場所に置いておけば、シナリオの格納先を移動させたりフォルダ名を変えても常に自分の居る場所を読み取るので、VBSの中身を修正する手間が減ります。</dd>
  <dd>　</dd>
  <dt>活用のポイント</dt>
  <dd>格納先フォルダの指定は不要です。</dd>
  <dd>その代わり、起動vbsは<strong>シナリオと同じフォルダ</strong>に置きます。</dd>
  <dd>
 
```vb
Public Const SCENARIO_FILE_FILTER ="*管理システムデータ印刷シナリオ*.ums6"
``` 

シナリオ検索用のフィルタはパターン１と同じです。
  </dd>
  <dd>　</dd>
  <dt>おすすめ点</dt>
  <dd>このVBSファイルのショートカットを作成してデスクトップに置けば、「デスクトップから簡単に起動」も再現できます。</dd>
  <dd>また、「ショートカットを作成」はVBSを開いて編集するよりも分かりやすく、現場の方にも対応いただきやすいです。</dd>
  <dd>　</dd>
  <dt>気になる点</dt>
  <dd>「VBSファイルをシナリオファイルと同じ場所に置く必要がある」という制限が増えます。</dd>
  <dd>また、シナリオファイルのベース名が変わったら結局VBSの中身を修正する必要は出てきます。</dd>
  <dd>（ただしVBSファイルはシナリオと同じ場所にある1つだけなので、それを書き直してショートカットを作り直せばいいのは利点と言えるかも）</dd>
  <dd>　</dd>
</dl>

```vb:シナリオ起動_格納先読み取り版.vbs
Option Explicit
'***********************************************************
'シナリオ情報（基本的にここだけ更新すればOK）
Public Const SCENARIO_FILE_FILTER ="*管理システムデータ印刷シナリオ*.ums6"
'***********************************************************
'デスクトップに生成されるWinActor.exeのショートカットの名前
Public Const WINACTOR_SHORTCUT_NAME = "WinActor.exe - ショートカット"

'開始
Call start_winactor_scenario

'============================================
'WinActorシナリオを実行
'============================================
Private Sub start_winactor_scenario()
    
    Dim objWSH: Set objWSH = CreateObject("WScript.Shell")
    Dim strScenarioFolderPath
    Dim strFileFilter
    Dim strScenarioFilePath
    Dim strWinActorFilePath
    Dim strWinActorShortcutPath
    Dim objWinActorShortcut
    Dim strCmd
    
    'シナリオフォルダ = 今いるフォルダ
    strScenarioFolderPath = Left(WScript.ScriptFullName, Len(WScript.ScriptFullName) - Len(WScript.ScriptName))
    
    '実行するシナリオファイルを取得 /キーワードを含む拡張子ums+1桁の数字となるファイル
    strScenarioFilePath = search_target_file(strScenarioFolderPath, SCENARIO_FILE_FILTER)
    If strScenarioFilePath = "" Then
        WScript.Echo "シナリオファイルが見つかりません。" _
                        & vbCrLf & "----------------------" _
                        & vbCrLf & "＜検索対象フォルダ＞" & vbCrLf & SCENARIO_FOLDER_PATH _
                        & vbCrLf & "＜検索キーワード＞" & vbCrLf & SCENARIO_FILE_FILTER & "（拡張子：ums）"
        WScript.Quit
    End If
    
    'デスクトップにあるWinActor.exeのショートカットを取得 / WinActor.exe - ショートカット.lnk
    strFileFilter = WINACTOR_SHORTCUT_NAME
    If LCase(Right(strFileFilter, 4)) <> ".lnk" Then
        strFileFilter = strFileFilter & ".lnk"
    End If
    strWinActorShortcutPath = search_target_file(objWSH.SpecialFolders("desktop"), strFileFilter)
    'パブリックデスクトップに置いてあるパターンもあるため再検索
    If strWinActorShortcutPath = "" Then
        strWinActorShortcutPath = search_target_file("C:\Users\Public\Desktop", strFileFilter)
    End If
    If strWinActorShortcutPath = "" Then
        WScript.Echo WINACTOR_SHORTCUT_NAME & "のショートカットがデスクトップに生成されていません。"
        WScript.Quit
    End If
    
    'ショートカットのリンク先（WinActor.exe）取得
    Set objWinActorShortcut = objWSH.CreateShortcut(strWinActorShortcutPath)
    strWinActorFilePath = objWinActorShortcut.TargetPath
    
    'コマンドライン作成/"WinActor.exeパス" -f "実行するシナリオファイルパス" -r
    strCmd = ""
    strCmd = strCmd & Chr(34) & strWinActorFilePath & Chr(34)
    strCmd = strCmd & " -f " & Chr(34) & strScenarioFilePath & Chr(34)
    strCmd = strCmd & " -r"
    
    'シナリオ起動
'    MsgBox strCmd
    objWSH.Run strCmd

End Sub

'============================================
'検索フォルダ内で、指定キーワードと指定拡張子が含まれるファイルを取得
'============================================
Private Function search_target_file(strSearchFolderPath, strFilter)
    Dim objFSO: Set objFSO = CreateObject("Scripting.FileSystemObject")
    Dim objRE: Set objRE = CreateObject("VBScript.RegExp")
    Dim f
    If Not objFSO.FolderExists(strSearchFolderPath) Then
        WScript.Echo "指定された検索フォルダが見つかりません。" _
                        & vbCrLf & "＜検索対象フォルダ＞" & vbCrLf & strSearchFolderPath
        WScript.Quit
    End If
    'ワイルドカード変換/ライブラリ「ファイル検索」のフィルタの調整と同じ
    strFilter = Replace(strFilter, "\", "\\")
    strFilter = Replace(strFilter, "+", "\+")
    strFilter = Replace(strFilter, ".", "\.")
    strFilter = Replace(strFilter, "|", "\|")
    strFilter = Replace(strFilter, "{", "\{")
    strFilter = Replace(strFilter, "}", "\}")
    strFilter = Replace(strFilter, "[", "\[")
    strFilter = Replace(strFilter, "]", "\]")
    strFilter = Replace(strFilter, "(", "\(")
    strFilter = Replace(strFilter, ")", "\)")
    strFilter = Replace(strFilter, "$", "\$")
    strFilter = Replace(strFilter, "^", "\^")
    strFilter = Replace(strFilter, "?", ".")
    strFilter = Replace(strFilter, "*", ".*")
    '正規表現パターン登録
    objRE.Pattern = "^" & strFilter & "$"
    For Each f In objFSO.GetFolder(strSearchFolderPath).Files
        If objRE.Test(f.Name) Then
            search_target_file = f.Path
            Exit For
        End If
    Next
End Function

```

## WinActor環境がない人向け（自宅学習、動作確認など）

個人でのWinActorの導入は、現時点ではちょっと難しいです。
（無料トライアルがありますが、メールがフリーアドレスだと駄目なようでした）

なので、WinAcror.exeの代わりにシナリオを起動させるコマンドを受け取り、メッセージを表示させるVBSファイルを作成しました。

##### ■ 用意
1. WinActor代用VBSファイル（コード後述）を作成し、適当な場所に保存する。
1.  代用VBSファイルのショートカットをデスクトップに作成する。
1.  ショートカットのファイル名を控える。
1.  シナリオ起動VBSの` Public Const WINACTOR_SHORTCUT_NAME = "●●" `にショートカット名を代入。
1.  シナリオ起動VBSをクリックする等で実行する。
1.  以下のようなメッセージが表示されれば正常に動作している。
![1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/416977/60ba1056-53b2-6ad1-7f8d-1dd30e549ef4.png)

##### ■ シナリオファイル（.ums6,.ums7など）の代用は？

この場合、実際にシナリオが動くことよりも

- シナリオファイルをちゃんと検索できること
- WinActor.exeにシナリオを起動させるコマンドが正しく送信されていること

が確認できればいいので、WinActorのない端末（自宅等）では適当なテキストファイルを作り、拡張子を「ums6」等で保存したものをシナリオファイルの代用に使いました。
<strong>WinActorが導入されている端末では逆に紛らわしいので、こういうことはせずに普通のシナリオファイルを作るなどして試してください。</strong>
![2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/416977/08ab3ed6-6bd2-f4bf-5029-0fd9c98ab8f9.png)

　

```vb:起動チェック用_WinActorExe代用.vbs
Option Explicit

Call Exec(WScript.Arguments)

'============================================
'WinActor.exeの代わりに起動コマンドを受け取り、メッセージを表示する
'============================================
Private Sub Exec(oParam)
    If oParam.Count < 1 Then
        WScript.Echo "引数が設定されていません。"
        WScript.Quit
    End If
    
    Dim objFSO: Set objFSO = CreateObject("Scripting.FileSystemObject")
    
    If oParam(0) <> "-f" Then
        WScript.Echo "シナリオファイルパスを指定するオプション(-f)を設定してください。"
        WScript.Quit
    End If
    
    Dim strScenarioFilePath
    Dim isVer7
    strScenarioFilePath = oParam(1)
    isVer7 = (Right(strScenarioFilePath, 1) = 7)
    If Not objFSO.FileExists(strScenarioFilePath) Then
        WScript.Echo "シナリオファイルパスが見つかりません。：" & strScenarioFilePath
        WScript.Quit
    End If
    
    'WinActorマニュアル すみずみ検索（https://winactor.biz/use/manual.html）
    'v7.4_操作マニュアル_WinActorの起動と終了.pdf
    'v6.3.2_操作マニュアル.pdf
    Dim aryOptions
    Dim i, subParam
    For i = 0 To oParam.Count - 1
        If i + 1 <= oParam.Count -1 Then
            subParam = Replace(oParam(i + 1), """","")
        End If
        Select Case oParam(i)
            'ver6,7にあり
            Case "-s"
                If Not IsOption_Ver7_4(subParam) Then
                    subParam = Mask_Password(subParam)
                    Call AddItem_ToArray(aryOptions, "-s：シナリオパスワード（" & subParam & "）")
                Else
                    WScript.Echo "シナリオパスワード(-s)を指定してください。"
                    WScript.Quit
                End If
            Case "-r"
                Call AddItem_ToArray(aryOptions, "-r：起動後、シナリオを実行します。")
            Case "-d"
                If objFSO.FileExists(subParam) Then
                    Call AddItem_ToArray(aryOptions, "-d：データ一覧ファイル（" & subParam & "）を開きます。")
                Else
                    WScript.Echo "データ一覧ファイル(-d)が見つかりません。"
                    WScript.Quit
                End If
            Case "-w"
                If IsNumeric(subParam) Then
                    Call AddItem_ToArray(aryOptions, "-w：" & subParam & "ミリ秒待機します。")
                Else
                    WScript.Echo "待機オプション(-w)の後は数字を指定してください。"
                    WScript.Quit
                End If
            Case "-x"
                If IsOption_Ver7_4(subParam) Then
                    Call AddItem_ToArray(aryOptions, "-x：シナリオ実行後にデータ一覧ファイルを上書き保存します。")
                Else
                     Call AddItem_ToArray(aryOptions, "-x：シナリオ実行後にデータ一覧ファイルを指定ファイル（" & subParam & "）で保存します。")
                End If
            Case "-e"
                Call AddItem_ToArray(aryOptions, "-e：シナリオの実行後に、WinActorを終了します。")
            Case "-t"
                Call AddItem_ToArray(aryOptions, "-t：WinActorを最小化した状態で起動します。")
            Case "-p"
                If Not IsOption_Ver7_4(subParam) Then
                    subParam = Mask_Password(subParam)
                    Call AddItem_ToArray(aryOptions, "-p：起動パスワード（" & subParam & "）")
                Else
                    WScript.Echo "起動パスワード(-p)を指定してください。"
                    WScript.Quit
                End If
            'ver7(.4)のみ
            Case "-ec"
                If Not isVer7 Then
                    WScript.Echo "終了ステータス返却(-ec)はver7以降のオプションです。"
                    WScript.Quit
                Else
                    Call AddItem_ToArray(aryOptions, "-ec：シナリオの実行後にWinActorを終了し、終了ステータスを返します。")
                End If
            Case "-sl"
                If Not isVer7 Then
                    WScript.Echo "ダイアログ非表示(-sl)はver7以降のオプションです。"
                    WScript.Quit
                Else
                    Call AddItem_ToArray(aryOptions, "-sl：WinActor起動時、またはシナリオ実行時に表示されるダイアログを非表示にします。")
                End If
            Case "-sa"
                If Not isVer7 Then
                    WScript.Echo "シナリオファイル別名保存後終了(-sa)はver7以降のオプションです。"
                    WScript.Quit
                Else
                    If Not IsOption_Ver7_4(subParam) Then
                        Call AddItem_ToArray(aryOptions, "-sa：指定ファイル名（" & subParam & "）でシナリオファイルを保存し、WinActorを終了します。")
                    Else
                        WScript.Echo "保存後シナリオファイル名(-sa)を指定してください。"
                        WScript.Quit
                    End If
                End If
        End Select
    Next
    If Not IsArray(aryOptions) Then
        Call AddItem_ToArray(aryOptions, "なし")
    End If

    Dim strPrompt
    If strScenarioFilePath = "" Then
        strPrompt = "シナリオファイルを指定してください。"
    Else
        strPrompt = "シナリオファイルの起動に成功しました。"
        strPrompt = strPrompt & vbCrLf
        strPrompt = strPrompt & vbCrLf & "■起動シナリオファイル："
        strPrompt = strPrompt & vbCrLf & strScenarioFilePath
        strPrompt = strPrompt & vbCrLf
        strPrompt = strPrompt & vbCrLf & "■起動オプション："
        strPrompt = strPrompt & vbCrLf & "・" & Join(aryOptions, vbCrLf & "・")
    End If
    MsgBox strPrompt

End Sub

'============================================
'1次配列に値を追加
'============================================
Private Sub AddItem_ToArray(ar, Item)
    If Not IsArray(ar) Then
        ar = Array(Item)
    Else
        ReDim Preserve ar(UBound(ar) + 1)
        ar(UBound(ar)) = Item
    End If
End Sub

'============================================
'オプションかどうか/ver7.4準拠
'============================================
Private Function IsOption_Ver7_4(subParam)
    Select Case subParam
        Case "-f", "-s", "-r", "-d", "-w", "-x", "-e", "-ec", "-t", "-p", "-od", "-ou", "-op", "-ot", "-st", "-sa"
            IsOption_Ver7_4 = True
        Case Else
            IsOption_Ver7_4 = False
    End Select
End Function

'============================================
'パスワードをマスクする
'============================================
Private Function Mask_Password(pw)
    Mask_Password = String(Len(pw), "*")
End Function


```

##### ■ 余談（反省）

当初、このWinActor代用VBSを作っていた際、ファイル名を「WinActor.exe.vbs」のような名前にしていました。

このことについて、<strong>実態の異なる拡張子をつけることは、セキュリティ的にも非常に危険である</strong>旨をご指摘いただき、ファイル名を上記サンプルのものに変更しました。

未然のうちでのご指摘に感謝するとともに、自分への戒めとして記載します。


## 終わりに

こんなことをしなくても、WinActorに標準搭載のショートカット作成機能で十分に便利ですし、そもそも起動ショートカットを必要としないシナリオもたくさんあるかと思います。

弊社はひとつのシナリオを複数台で同時実行するシナリオが多く、「いつでもどこでも誰でも手軽に実行できる」メリットがかなり大きいため、これで割と助かりました。

もちろんこのコードは一例で、レビューを受けたわけでもないので、方向性だけでも使えそうだと思われたらぜひどんどん手直ししたり一から書き直したりしていってください。

VBS、書けるとWinActorのライブラリの大半がもっと自分の思い通りになるし、読めるとライブラリの抜け道を見つけたりもできるので、まだふれたことのない方には結構おすすめです。

ここまで見ていただき、ありがとうございました。