IEの起動
$url = "http://hogehoge.com"
# シェルを取得
$shell = New-Object -ComObject Shell.Application
# IE起動
$ie = New-Object -ComObject InternetExplorer.Application
# 可視化
$ie.Visible = $true
# HWNDを記憶
$hwnd = $ie.HWND
# URLオープン(キャッシュ無効)
$ie.Navigate($url,4)
# IE再取得
while ($ie.Document -isnot [mshtml.HTMLDocumentClass]) {
$ie = $shell.Windows() | ? {$_.HWND -eq $hwnd}
}
IEの保護モードがオフの時は、-ComObject
にInternetExplorer.ApplicationMedium
を指定するか、管理者モードで起動しないと、URLを開いた後にIEが操作できなくなる。しかし、保護モードかどうかを知るのが難しく、管理者モードで起動できるとも限らない。
最後のループはそれを解消するためのもので、$shell.Windows()
からIEを取得しなおすと保護モードがオフでもIEが操作できる。ループにしたのは、$ie.Document
が取得できない場合があるため。
DOMの操作
function OverrideMethod ([mshtml.HTMLDocumentClass]$Document) {
$doc = $Document | Add-Member -MemberType ScriptMethod -Name "getElementById" -Value {
param($Id)
[System.__ComObject].InvokeMember(
"getElementById",
[System.Reflection.BindingFlags]::InvokeMethod,
$null,
$this,
$Id
) | ? {$_ -ne [System.DBNull]::Value}
} -Force -PassThru
$doc | Add-Member -MemberType ScriptMethod -Name "getElementsByClassName" -Value {
param($ClassName)
[System.__ComObject].InvokeMember(
"getElementsByClassName",
[System.Reflection.BindingFlags]::InvokeMethod,
$null,
$this,
$ClassName
) | ? {$_ -ne [System.DBNull]::Value}
} -Force
$doc | Add-Member -MemberType ScriptMethod -Name "getElementsByTagName" -Value {
param($TagName)
[System.__ComObject].InvokeMember(
"getElementsByTagName",
[System.Reflection.BindingFlags]::InvokeMethod,
$null,
$this,
$TagName
) | ? {$_ -ne [System.DBNull]::Value}
} -Force
return $doc
}
$document = OverrideMethod($ie.Document)
$button = $document.getElementById("login_button")
$button.click()
PowershellからgetElementById()
などを実行すると、メソッドは存在しているのにエラーになる場合がある。こういう場合は、InvokeMember()
経由で実行するようメソッドをオーバーライドする。
メソッドのオーバーライドはAdd-Member
に-Force
を指定すればできる。ただし、もとのオブジェクトを直接オーバーライドするわけではなく、いったんPSObjectでラップしてそのメソッドをオーバーライドしている。そのため、最初のオーバーライド時に-PassThru
を指定してラップしたオブジェクトを受け取る必要がある。
なお、getElementById()
などは該当するノードがないと、$null
ではなく[System.DBNull]::Value
を返してくるので、扱いやすくするため除外している。
読み込み待ち
while ($ie.busy -or $ie.readystate -ne 4) {
Start-Sleep -Milliseconds 100
}
読み込み待ちのサンプルとして上記のようなコードをよく見かけるが、これだとページのリダイレクトやJavaScriptによる動的生成に対応できない。以下のように、実際に操作したいオブジェクトを取得してみる方が確実。
while ($true) {
$button = $document.getElementById("login_button")
# 画面が表示された
if ($button) {break}
Start-Sleep -Milliseconds 100
}
$button.click()
既存のウインドウを取得する
$url = "http://hogehoge.com"
$ie = $shell.Windows() |
? {$_.Name -eq "Internet Explorer"} |
? {$_.LocationURL -like $url} |
Select-Object -First 1
ウインドウの最大化
$dll_info = '[DllImport("user32.dll")] public static extern bool ShowWindowAsync(IntPtr hWnd, int nCmdShow);'
Add-Type -MemberDefinition $dll_info -Name NativeMethods -Namespace Win32
[Win32.NativeMethods]::ShowWindowAsync($ie.HWND, 3) | Out-Null
ウインドウをアクティブ化する
Add-Type -AssemblyName Microsoft.VisualBasic
$window_process = Get-Process -Name "iexplore" | ? {$_.MainWindowHandle -eq $ie.HWND}
[Microsoft.VisualBasic.Interaction]::AppActivate($window_process.ID) | Out-Null
※Out-Null
は本当は不要なのだが、行が表示されなくなるので仕方なく付けた。二重コロンがダメ?
タブを前面に表示する
Add-Type -AssemblyName Microsoft.VisualBasic
Add-Type -AssemblyName System.Windows.Forms
while ($true) {
# ウインドウプロセス取得
while ($true) {
$window_process = Get-Process -Name "iexplore" | ? {$_.MainWindowHandle -eq $ie.HWND}
if ($window_process) {break}
Start-Sleep -Milliseconds 100
}
# ウインドウをアクティブ化
[Microsoft.VisualBasic.Interaction]::AppActivate($window_process.ID)
if ($window_process.MainWindowTitle -ne "$($ie.Document.title) - Internet Explorer") {
# タブ切替
[System.Windows.Forms.SendKeys]::SendWait("^{TAB}")
} else {
# タブが前面に表示されている
break
}
}
ウインドウプロセスのMainWindowTitle
と$ie.Document.title
を比較すれば、タブが前面に表示されているかが分かる。あとは目的のタブが前面に表示されるまでタブを切り替えれば良い。
ウインドウプロセスはタブを切り替える度に取得し直さなければならず、タブを切り替えてから間がないと取得に失敗する。
タブの切替はショートカットキーで行わなければならず、念のため毎回ウインドウをアクティブ化している。
キーイベントに反応するフォームの入力
Add-Type -AssemblyName Microsoft.VisualBasic
$username = "user1"
$input = $document.getElementById("user")
# ウインドウのアクティブ化
[Microsoft.VisualBasic.Interaction]::AppActivate($window_process.ID) | Out-Null
# 入力欄のクリア
$input.value = ""
# フォーカスを当てる
$input.focus()
# キー入力
[System.Windows.Forms.SendKeys]::SendWait($username) | Out-Null
# 入力が反映されたか確認
while ($input.value -ne $username) {
Start-Sleep -Milliseconds 100
}
キーイベントに反応してバリデーションチェックが行われ送信ボタンが押せるようになるフォームの場合、value
に直接値を代入しても送信ボタンが押せるようにならないため、キー入力を再現する必要がある。
事前に値が入力されているとその後に追加されてしまうので、まず入力欄をクリアした後にキー入力する。また、文字列のキー入力は反映されるのに時間がかかるので、最後のループで入力が反映されたことを確認している。
※Out-Null
は本当は不要なのだが、行が表示されなくなるので仕方なく付けた。