VBAのSeleniumBasicで起動したChromeを、手作業をはさんだ後に再度Seleniumで操作する。

# 重要な追記
すみません、もっとずっとシンプルな方法がありました。こちらで！
https://qiita.com/shibahead/items/0a17efaa70995756261c

ただし、↓下記の方法も利点があって、
下記の方法では「普段使っているChromeのプロファイルをそのまま」使えます。
つまり普段のChromeでログイン状態になっているサイトがあれば、Chromeもログイン状態になります。

かつ、普段のChromeとは異なる特殊なChromeを立ち上げるので、
普段のChromeと同時利用ができます。（--user-data-dirを使う方法は普段のChromeとの同時利用がしづらい）

# やりたいこと
VBAのSelenimBasicでChromeを自動操作するときに、
1. SeleniumでChromeを起動して、自動操作
1. いったん、人間が手作業をはさむ
1. 同じChromeを、再度Seleniumで操作したい

ということがあります。図にするとこんな感じ↓。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/100646/22d2ec3a-cf8a-f426-a3cc-7ff57eeae86f.png)

これ、人間の手作業が終わるまでChromeにループで待機させても良いのですが、
ループで見張るフラグが無いとか、ループが動き続けるのも嫌、ということがあるので、
一旦VBAを終了して、手作業をはさんだ後で、別のVBAで同じChromeを捕まえられると助かることがあります。

しかし普通のSeleniumBasicの使い方では、
一度VBAを中断したら、同じChromeをSeleniumで捕まえて操作することはできません。（IEならできたのですが）
そこで以下の方法が必要となります。

# 本記事の概要
## 参考URL
下記のURLにて「特殊なショートカットで起動したChromeを、SeleniumBasicで操作する方法」の紹介＆コードがあります。
https://vba-labo.rs-techdev.com/archives/1234
## 本記事の提案
本記事では上記を参考にさせていただきつつ、ショートカットを使わずに、
「VBAのSeleniumでChrome起動　→　手作業　→　同じChromeを再度Seleniumで操作する」というパターンのコードを提案します。

# SeleniumBasicの準備
SeleniumBasicの入手＆参照設定は済んでいることが前提です。

# コード
コードは以下の通りです。全部で２つのコードがあります。
- Start_Chrome　：　Chromeを後でSeleniumで捕まえられるように、特殊な方法で起動するコード
- Get_Opened_Chrome　：　上記のChromeを、実際にSeleniumで捕まえる関数

まず、「Chromeを後で再度捕まえられるように、特殊な方法で起動する」コードです。
``` vba
Sub Start_Chrome()

    '開き済みでも取得できる特殊なChromeを開く、そのためのバッチファイルを作る
    Set objshell = CreateObject("WScript.Shell")
    mypath = objshell.SpecialFolders("MyDocuments") & "\Start_Chrome.bat"
    
    '特殊なChromeを起動する（が、直接実行するとExcelセキュリティによりエラーになる）
    'ので、バッチファイルを作成し実行する
    Open mypath For Output As #1
        If Dir("C:\Program Files (x86)\Google\Chrome\Application\chrome.exe") = "" Then
            Print #1, """C:\Program Files\Google\Chrome\Application\chrome.exe"" -remote-debugging-port=9222 --user-data-dir=C:\Temp_ForChrome"
        Else
            Print #1, """C:\Program Files (x86)\Google\Chrome\Application\chrome.exe"" -remote-debugging-port=9222 --user-data-dir=C:\Temp_ForChrome"
        End If
    Close #1
    'バッチファイルを実行して特殊なChromeを起動
    Shell mypath, windowstyle:=vbHide
    
End Sub

```

次に上記のChromeを、実際にSeleniumで捕まえる関数です。
``` vba
Function Get_Opened_Chrome()
    '開き済みのChromeをとっつかまえる
    Dim Driver As New Selenium.WebDriver
    Driver.SetCapability "debuggerAddress", "127.0.0.1:9222"
    Driver.Start "chrome"
    Set Get_Opened_Chrome = Driver
End Function
```

以上です。実際に２つのコードを使って「やりたいこと」のコードを書くと、
``` vba
'最初に実行するVBA
sub Macro1()
    'Chrome起動
    call Start_Chrome

    ’起動したChromeを捕まえて、Driverに入れる
    Set Driver = Get_Opened_Chrome

　　’Driverでどこかにアクセスして、何か作業があれば作業させる。
    Driver.Get "https://*****"

    ’一旦VBA１はおわり。このあと人間の手作業をはさむ。
end sub

'ここで、人間が手作業する。
’手作業が終わったら、次のMacro2を実行する↓↓

sub Macro2()

    ’さっきまでのChromeを再度捕まえて、Driverに入れる
    Set Driver = Get_Opened_Chrome

    ’何か作業させるなら、させる。

end sub

```

# Excelを閉じてもChromeが残る
副次的な効果というか、副作用というか、
上記の方法で起動したChromeは、VBAを実行したExcelを終了しても
Chromeが終了せずにそのまま残ります。
VBAとChromeが切り離されて動作する感じですね。

# 【応用】特殊なChromeが既に存在しているかを確認する

これまでの方法で「後から捕まえられる特殊なChrome」を使う方法を提示しましたが、
この「特殊なChrome」が既に存在するかどうか、判定したいことがあります。
存在すればそれを使うし、存在しなければ新規で立ち上げる、といった場合です。

この「Chromeがあるか？」の判定には、WindwowsAPIのFindWindowExを使います。
FindWindowExはアプリケーション名から該当のウィンドウの存在を確認することができますが、
ここで、「特殊なChrome」と「普通のChrome」のウィンドウ名がどちらも「Chrome_WidgetWin_1」で一緒のため、区別がつかないという問題が生じます。

この対策として、はじめにGet_Opened_Chromeで特殊なChromeを捕まえたときに、そのウィンドウに「特殊なChromeですよ」というプロパティをつけておきます。これにより後から参照するときに、このプロパティの有無で、ユーザーが自分で開いているChromeなのか、VBAにより作られた特殊なChromeなのかを区別することができます。
実際のコードは以下のようになります。
Start_Chromeは変わりません。
次に、Get_Opened_Chromeを以下のように変更します。
```vb
'WindowsAPIを使うためのおまじない
Public Declare PtrSafe Function SetForegroundWindow Lib "user32.dll" (ByVal HWnd As Long) As Long
Public Declare PtrSafe Function GetActiveWindow Lib "user32" () As Long
Public Declare PtrSafe Function ShowWindow Lib "user32" (ByVal HWnd As Long, ByVal nCmdShow As Long) As Long
Public Declare PtrSafe Function GetForegroundWindow Lib "user32" () As Long
Public Declare PtrSafe Function FindWindowEx Lib "user32" Alias "FindWindowExA" (ByVal hWndParent As Long, ByVal hwndChildAfter As Long, ByVal lpszClass As String, ByVal lpszWindow As String) As Long
Public Declare PtrSafe Function GetWindowText Lib "user32" Alias "GetWindowTextA" (ByVal HWnd As LongPtr, ByVal lpString As String, ByVal cch As Long) As LongPtr
Public Declare PtrSafe Function CloseWindow Lib "user32" (ByVal HWnd) As Long
Public Declare PtrSafe Function SetProp Lib "user32" Alias "SetPropA" (ByVal HWnd As LongPtr, ByVal lpString As String, ByVal hData As LongPtr) As Long
Public Declare PtrSafe Function GetProp Lib "user32" Alias "GetPropA" (ByVal HWnd As LongPtr, ByVal lpString As String) As LongPtr

'変更したGet_Opened_Chrome
Function Get_Opened_Chrome()
    '開き済みのChromeをとっつかまえる
    Dim Driver As New Selenium.WebDriver
    Driver.SetCapability "debuggerAddress", "127.0.0.1:9222"
    Driver.Start "chrome"
    Set Get_Opened_Chrome = Driver
    
    '特殊なChromeのWindowに、他のChromeと区別するためのプロパティを付与する
    h = GetForegroundWindow()
    Debug.Print "setprop", SetProp(h, "ChromebyVBA", 1)
    Debug.Print h
End Function

```
そして以下が、「特殊なChromeが既に存在するか？」を判定する関数です。
FindWindowExにより「Chrome_WidgetWin_1」というクラスのWindow（つまり全部のChrome）をループしつつ、「ChromebyVBA」という「特殊なChrome」だけが持つプロパティをチェックしています。
これが存在すればループは終了し返り値はそのウィンドウのハンドル値、無ければ返り値は0になります。
（返り値はTrue,Falseでも良いのですが、ハンドル値も利用しやすいので）
```vb
 Function ChromeOpenHwnd()
    '特殊な形のChromeが開いているか確認する関数
    '判定は、Chrome起動後にWindowにPropertyを設定している。
    'そのためWindowが開いている限り判別可能となる。
    ChromeOpenHwnd = 0
    h = FindWindowEx(0, 0, "Chrome_WidgetWin_1", vbNullString)
    Do Until h = 0
        prop = GetProp(h, "ChromebyVBA")
        If prop > 0 Then
            ChromeOpenHwnd = h
            Exit Do
        End If
        '次のウィンドウを検索
        h = FindWindowEx(0, h, "Chrome_WidgetWin_1", vbNullString)
    Loop
End Function
```

上記を用いて、特殊なChromeが無いときは特殊なChromeを起動する、既に起動済みのときはそのChromeを捕まえる、といったコードは以下の様になります。
```vb
Sub test_chrome()
  If ChromeOpenHwnd = 0 Then
    Debug.Print "特殊なChromeスタートします"
    Call Start_Chrome
  End If
    
  Set Driver = Get_Opened_Chrome
  Driver.Get "https://google.com"
End Sub
```


# おわり
お疲れさまでした。