2
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

【Excel】Mac版VBAでShellを使用する方法

Last updated at Posted at 2022-04-18

はじめに

Excelでwebブラウザの操作を自動化する最小機能のみ実装したフレームワークの「TinySeleniumVBA」があります。
これは今のところActiveXを使用しているため、Windows専用になっています。ActiveXの部分を取り除き代替部分をVBA-toolsライブラリー群を使えば、Mac上のExcelで動作するかもと取り組みました。
VBA-toolsを用いてWindows上では動作するようになったのですが、残念ながらMacではエラーで動作しませんでした。

調査した結果、WebDriver(chromedriver or msedgedriver)のプロセスが起動していないことが分かりました。
Shell関数の戻り値 0 にはなっていないため、エラーは出ていません。

' Start WebDriver executable
If Shell(driverPath, vbMinimizedNoFocus) = 0 Then
    MsgBox "Failed in starting WebDriver"
    End
End If

環境

  • Mac Book Pro(2.3 GHz 8コアIntel Core i9) Monterey 12.2.1
  • Microsoft Office Home and Student 2019

MacのShell関数は使えない

Macintoshコンピュータでは、MacID関数を使用して、アプリケーションの名前の代わりにアプリケーションの署名を指定できます。次の例では、Microsoft Wordの署名を使用しています。Shell MacID("MSWD")
https://docs.microsoft.com/ja-jp/office/vba/language/reference/user-interface-help/shell-function

MacIDって検索してみたけど、よく分からない。他の方法を模索する。

Shellの代替案

MacScript関数を利用する方法

ネットで検索するとShell関数の代わりにMacScript関数を使用する方法が散見される。
しかし、MacScript関数は廃止され、サポートされなくなりました。

MacScript について
以前のバージョンの Office for Mac には、インラインの Applescript をサポートしている MacScript と呼ばれるコマンドが実装されていました。 このコマンドは、Office 2016 for Mac でもまだ存在していますが、MacScript は使用されなくなりました。 サンドボックス制限のため、MacScript コマンドは、Finder などのほかのアプリケーションを Office 2016 for Mac で呼び出すことができません。 Office 2016 for Mac のアプリでは、MacScript コマンドの代わりに AppleScriptTask コマンドを使用することをお勧めします。
https://docs.microsoft.com/ja-jp/office/vba/office-mac/applescripttask

エラーにはならないが、常にFALSEが返ってくるとのこと。

Excel2011ではVBA内にAppleScriptをインラインで記述し、MacScript関数で実行する事ができましたが、Excel2016以降、セキュリティ上の観点から廃止されました。Excel2019で実行するとエラーにはなりませんが、常にFALSEが帰ってきます。
Excel 2019 for Mac OS判定とAppleScriptTask利用方法

AppleScriptTask関数を利用する方法

Office 2016 for Mac 以降では、MacScript関数の代わりに AppleScriptTask関数を使用する。
先述したTinySeleniumVBAのShell関数は、AppleScriptTask関数に置き換えることで対応できた。

AppleScriptTask関数を利用する手順は2段階となります。

  1. スクリプトファイルを「~/Library/Application Scripts/com.microsoft.Excel/」に保存する。
  2. VBA内でAppleScriptTaskからスクリプトファイルを呼び出す。

VBAではライブラリーフォルダにアクセスできないので配布するとなると少し面倒です。

最初、Shellが同期した状態で動いたため次のプロセスを終了するまで先に進まなかったのですが、下記サイトの方法で「" &"」を付けることで非同期で動かすことが出来ました。

【2022/05/05追記】
出力結果を捨ててしまうため、"> /dev/null 2>&1"を付けるのをやめました。 2022/05/21 非同期のみ付け直しました。
shell.scptとして、同期と非同期のハンドラ(関数)に分けました。

shell.scpt
on synchandler(param)
    try
        set ret to do shell script param
        return ret
    on error number n
        return "error " & n
    end try
end synchandler

on ansynchandler(param)
    try
        set ret to do shell script param & "> /dev/null 2>&1 &"
        return ret
    on error number n
        return "error " & n
    end try
end ansynchandler
Example.bas
Public Sub main()
    ' Start WebDriver 
    Driver.Chrome "/usr/local/bin/chromedriver"
WebDriver.cls
' Start WebDriver
Public Sub Start(ByVal driverPath As String, ByVal driverUrl As String, Optional ByVal driverName As String = vbNullString)
    'Start WebDriver executable
    #If Mac Then
        Dim scriptResult As String
        scriptResult = AppleScriptTask("shell.scpt", "ansynchandler", driverPath)
    #Else
        If Shell(driverPath, vbMinimizedNoFocus) = 0 Then
            MsgBox "Failed in starting WebDriver"
        End If
    #End If

実行することで、chromedriverが起動していることが分かります。

yaju@hi ~ % ps aux | grep chromedriver  
yaju         12435   0.0  0.0 34132100    916 s001  S+    7:39PM   0:00.00 grep chromedriver
yaju         11642   0.0  0.0 67343788   4852   ??  S     7:33PM   0:00.02 /usr/local/bin/chromedriver

C ライブラリの popen 関数を利用する方法

VBA-WEBライブラリーの中にも、curlを使用するためにExecuteInShell関数が用意されています。

WebHelper.bas
#If Mac Then
#If VBA7 Then
Private Declare PtrSafe Function web_popen Lib "/usr/lib/libc.dylib" Alias "popen" (ByVal web_Command As String, ByVal web_Mode As String) As LongPtr
Private Declare PtrSafe Function web_pclose Lib "/usr/lib/libc.dylib" Alias "pclose" (ByVal web_File As LongPtr) As LongPtr
Private Declare PtrSafe Function web_fread Lib "/usr/lib/libc.dylib" Alias "fread" (ByVal web_OutStr As String, ByVal web_Size As LongPtr, ByVal web_Items As LongPtr, ByVal web_Stream As LongPtr) As LongPtr
Private Declare PtrSafe Function web_feof Lib "/usr/lib/libc.dylib" Alias "feof" (ByVal web_File As LongPtr) As LongPtr
#Else
Private Declare Function web_popen Lib "libc.dylib" Alias "popen" (ByVal web_Command As String, ByVal web_Mode As String) As Long
Private Declare Function web_pclose Lib "libc.dylib" Alias "pclose" (ByVal web_File As Long) As Long
Private Declare Function web_fread Lib "libc.dylib" Alias "fread" (ByVal web_OutStr As String, ByVal web_Size As Long, ByVal web_Items As Long, ByVal web_Stream As Long) As Long
Private Declare Function web_feof Lib "libc.dylib" Alias "feof" (ByVal web_File As Long) As Long
#End If
#End If

'
' Execute the given command
'
' @internal
' @method ExecuteInShell
' @param {String} Command
' @return {ShellResult}
''
Public Function ExecuteInShell(web_Command As String) As ShellResult
#If Mac Then
#If VBA7 Then
    Dim web_File As LongPtr
#Else
    Dim web_File As Long
#End If

    Dim web_Chunk As String
    Dim web_Read As Long

    On Error GoTo web_Cleanup

    web_File = web_popen(web_Command, "r")

    If web_File = 0 Then
        ' TODO Investigate why this could happen and what should be done if it happens
        Exit Function
    End If

    Do While web_feof(web_File) = 0
        web_Chunk = VBA.Space$(50)
        web_Read = CLng(web_fread(web_Chunk, 1, Len(web_Chunk) - 1, web_File))
        If web_Read > 0 Then
            web_Chunk = VBA.Left$(web_Chunk, web_Read)
            ExecuteInShell.Output = ExecuteInShell.Output & web_Chunk
        End If
    Loop

web_Cleanup:

    ExecuteInShell.ExitCode = CLng(web_pclose(web_File))
#End If
End Function

この方法で/usr/local/bin/chromedriverを起動しようとしたが出来ませんでした。
しかし、lsやcurlやPerlなどのコマンドはExecuteInShell関数で動作します。

Perlが動くならとターミナル上でPerlのワンライナーでchromedriverが起動できるのを確認後、VBA側で同じようにchromedriverを起動させようと下記プログラムを試しました。しかし、エラーにならず処理は通るもののchromedriverは起動されませんでした。

web_Command = "perl -e 'system(""/usr/local/bin/chromedriver &"")'"
scriptResult = WebHelpers.ExecuteInShell(web_Command)

lsやcurlやPerlなどが格納されている/usr/binフォルダにあるものはセキュリティ上は動作可能になっているのかも知れませんね。

最後に

配布に問題があるものの、chromedriverなどの外部プロセスを動かすには、AppleScriptTask コマンドを使用しAppleScript スクリプト側で動作させるしかなさそうです。

2
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?