Windows11でまとめてタブを開いたりするVBS

相変わらずブログが放置状態になっていますが、今だけ使える旬の内容なので久しぶりに更新することにしました。Windows11 の新機能のタブはそのうちより改良され、表題のようなことはしなくても良くなると思うので。

同じような VBS が Reddit にあって、またそれを紹介しているブログもあったりしますが、結構単純な感じであったため、私の重いと感じている環境では期待した動作にはなりませんでした。なので、なるべく SendKeys を使わない形で、より確度の高い動作が出来るように、久しぶりに VBScript を書きました。

「久しぶりに」と言っているように、ブログを更新していない間すっかり Python を覚え、今はそちらをメインで使っています。IE でブラウザの自動化をしてたりしましたが、こちらのサポートも終わってしまったので、Python + Selenium に移行しています。

掲載する VBS は二つで、最初に作った Reddit にあるのと同じような新規ウィンドウを立ち上げて指定のパスを開くパターン。もう一つは、既存のウィンドウ (エクスプローラー) に対し新規タブを開き指定のパスを開くパターンです。この記事を書いている間も大きく書き換えて記事も書き直したり、なかなか確定出来ませんでした(笑)

注意点や味噌など解説していきます。

新規ウィンドウでまとめてタブを開く VBS

主に起動直後などでまとめて指定のタブを開きたい場合、使えると思います。ただ最近エクスプローラーの設定を見てて気付きましたが、「ログオン時に以前のフォルダー ウィンドウを表示する」という項目を今更知ったので、この設定をオンにするなら必要性が薄れるかも(笑)

' ExplorerTabsInNewWin.vbs
Option Explicit

Dim sl, ws, LpLim, LpSlp, i, j, args, iniWinCnt
Set sl = CreateObject("Shell.Application")
Set ws = CreateObject("WScript.Shell")
Set args = WScript.Arguments
If args.Count = 0 Then WScript.Quit

Const LOC_NAME = "ホーム"
LpLim = 50: LpSlp = 200
iniWinCnt = sl.Windows.Count

ws.Run ("explorer.exe")
WaitNewWin

For i = 0 To args.Count - 1
	For j = sl.Windows.Count - 1 To 0 Step -1
		With sl.Windows(j)
			If InStr(TypeName(.document), "IShellFolderViewDual") And .LocationName = LOC_NAME And j > iniWinCnt - 1 Then
				.Navigate args(i)
				Exit For
			End If
		End With
	Next
	if i < args.Count - 1 Then
		ws.AppActivate "Explorer"
		WScript.Sleep 200
		ws.SendKeys "^t"
		WScript.Sleep 300
		WaitNewWin
	End If
Next

Sub WaitNewWin()
	Dim i, j
	For i = 1 To LpLim
		For j = sl.Windows.Count - 1 To 0 Step -1
			With sl.Windows(j)
				If InStr(TypeName(.document), "IShellFolderViewDual") And .Visible And .LocationName = LOC_NAME And j > iniWinCnt - 1 Then Exit Sub
			End With
		Next
		WScript.Sleep LpSlp
	Next
	WScript.Quit
End Sub

初めは Reddit のもののように、スクリプトの中に開きたいパスを書いていましたが、特に前述の既存ウィンドウの新規タブに対し指定のパスを開く VBS において、それを多くのパスで Win + R を使ってやりたかったことから、上記のスクリプトが書かれた VBS ファイルを多数コピーしてパスだけが違うという状態は非合理に思えたため、パスだけスクリプトに渡してあげる仕様にしました。ショートカット (.lnk) のプロパティのリンク先で、「C:\ExplorerTabsInNewWin.vbs C:\Users」のような感じで引数を与えます。開きたいタブ分、空白で区切って引数のパスを増やします。(Program Files などパス自体に空白がある場合は勿論ダブルコーテーションが必要です。)

大まかな流れは、新規ウィンドウ・タブが開くまで待機し、開いたことが確認出来たら指定のパスを開く、といった感じの繰り返しです。最後のウィンドウハンドルを取得してそれを元に新規ウィンドウの有無を判定していたりしましたが、結局問題がある場合があってウィンドウを開く前のウィンドウ数と比較することにしました。

以下は詳細や細かな注意点などです。

WaitNewWin

新規ウィンドウが生成されるまで待機します。判断する条件は、開いた際のウィンドウタイトルと初期ウィンドウ数より上であるか、です。例えばスクリプト実行前に開いてるウィンドウに「ホーム」があっても、それは除外されることになります。

For Each ではなく For 文の逆回転を使ってます。本当は For Each を使いたかったのですが、VBScript では For Each の逆回転が出来ないので致し方なく For にしました。なので一応注意なのですが、場合によって「オブジェクトがない」というエラーが出る可能性はあります。「sl.Windows.Count - 1」と現在のウィンドウ数の MAX から 0 に向けて繰り返してるので、構文上はエラーが起きるはずはないのですが、VBA のウォッチウィンドウで色々検証していた時、何故かこの sl.Windows.Count に入ってる数と その sl.Windows にぶら下がってる Item の数が一致しない場合があり、その時はエラーが出ました。(例えば .Count に 10 という数字が入ってるが実際ぶら下がってる Item が何故か 7 つしかない場合に i に 10 が入った時、当然エラーとなる) ただエラーが出たのはその時だけで、実用していて今のところ同じエラーはありません。

逆回転している理由は、新規ウィンドウは配列の後方にある訳ですので、大きい数字から開始した方が効率的に思えたからです。前方から始めると余分なループが発生します。

永久ループになってしまうのを避けるため、外側の For でリミットを設けています (LpLim)。リミットに達したら VBScript 自体そのまま終了させるようにしています。

追記

上記の数の不一致の問題、恐らくエクスプローラーが強制終了した時に起こることが分かりました。上記でエラーが出たら、タスクマネージャーを開いてエクスプローラーのプロセスをすべて強制終了し、エクスプローラーを再起動すると問題なく動作します。

その他

Reddit のものと違って、パスは SendKeys で送って Enter で叩くのではなく、Navigate でナビゲートさせます。

SendKeys で開くのは Ctrl + T のみです。この部分も新規タブを SendKeys を使わず出来ないかと検証しましたが、後述しますがウォッチウィンドウで見る限り新しいタブ機能に関しては別の仕組みになっているように思え、VBScript 側からはどうにも制御出来なさそうでした。

既存ウィンドウでタブを開く時に、指定のパスを開く VBS

私は主に指定のパスを開きたい時、前述のように Win + R からよく開いてます。ただこの際エクスプローラーのショートカットを叩いても、新規ウィンドウで開いてしまう訳です。これが何とかならないかとこのバージョンも作りました。

何れこれも将来の Windows アップデートで、ショートカットに何らかの引数を付けたら既存ウィンドウにタブを追加して開く、など機能が拡張するのだろうと個人的には予想しています。

主な目的は既存ウィンドウに対しての実行ですが、既存ウィンドウが無かった時は新規ウィンドウを起動して実行します。(つまりその場合一つ目のスクリプトとほぼ同じ動作) 謂わば統合した形になります。

デメリットは、前述のスクリプトも含め、重く感じます…。もし Windows 標準の機能ならもっさり感はないと思うのだけど。ただ重さは環境によると思います。Ryzen 3700X で仮想 OS を常時起動してる環境では重いですが、3900X ではそこまで重いと感じません。

' ExplorerTabsInExistWin.vbs
Option Explicit

Dim sl, ws, LpLim, LpSlp, i, j, args, w, flgNew, iniWinCnt
Set sl = CreateObject("Shell.Application")
Set ws = CreateObject("WScript.Shell")
Set args = WScript.Arguments
If args.Count = 0 Then WScript.Quit

Const LOC_NAME = "ホーム"
LpLim = 50: LpSlp = 200
flgNew = True
iniWinCnt = sl.Windows.Count

For Each w In sl.Windows
	If InStr(TypeName(w.document), "IShellFolderViewDual") And w.Visible Then flgNew = False: Exit For
Next

' 新規の場合
If flgNew Then
	ws.Run ("explorer.exe")
	WaitNewWin
' 既存のウィンドウで開く場合
Else
	OpenTab
End If

For i = 0 To args.Count - 1
	For j = sl.Windows.Count - 1 To 0 Step -1
		With sl.Windows(j)
			If InStr(TypeName(.document), "IShellFolderViewDual") And .LocationName = LOC_NAME And j > iniWinCnt - 1 Then
				.Navigate args(i)
				Exit For
			End If
		End With
	Next
	If i < args.Count - 1 Then OpenTab
Next

Sub OpenTab()
	If flgNew Then
		ws.AppActivate "Explorer"
	Else
		For Each w In sl.Windows
			If InStr(TypeName(w.document), "IShellFolderViewDual") And w.Visible Then ws.AppActivate w.LocationName
		Next
	End If
	WScript.Sleep 200
	If Not flgNew Then
		ws.SendKeys "{F4}"
		WScript.Sleep 100
	End If
	ws.SendKeys "^t"
	WScript.Sleep 300
	WaitNewWin
End Sub

Sub WaitNewWin()
	Dim i, j
	For i = 1 To LpLim
		For j = sl.Windows.Count - 1 To 0 Step -1
			With sl.Windows(j)
				If InStr(TypeName(.document), "IShellFolderViewDual") And .Visible And .LocationName = LOC_NAME And j > iniWinCnt - 1 Then Exit Sub
			End With
		Next
		WScript.Sleep LpSlp
	Next
	WScript.Quit
End Sub
Open Tab

新規ウィンドウに対し行う時は、ws.AppActivate "Explorer" で最前面化します。しかし既存のウィンドウは最後に開いたものに対して実行したいため、具体的に指定します。

味噌の一つは、具体的に指定と言っても、For で抜けることなく最後まで回していることです。これには理由があり、当初は最後に開いたウィンドウ (タブ) の LocationName を指定して最前面化していました。しかしそれが上手く動作しない場合があり、原因を探ってみると、最後のタブ以外に自分が手動でフォーカスを移していた場合に起こることが分りました。

例えば A, B, C というタブがあり、最後に開いたタブとしては C だけど、自分で作業上 B のタブへフォーカスしていた場合、C を最前面化させたくても、出来ません。それでは B にフォーカスがあることを取得し、B の LocationName を指定して最前面化すれば良いのではとも思いましたが、前述のウォッチウィンドウの通り「どのタブにフォーカスがあるか」などのプロパティはない模様です。タブ関係のプロパティは全く無さそうです。

そこで、一つに指定せず通常回転で全部回せば、どこかで B にヒットしウィンドウを最前面化出来るなと思い浮かびました。二つ目の味噌は、他の部分では逆回転が主な回し方ですが、逆にここでは通常回転であることです。例えば B と同じパスだけどウィンドウが違い、且つそっちの方が古いウィンドウの場合、順番的に逆回転だとその古いウィンドウの方が最終的に最前面されますが、通常回転だと新しい方が最前面化されます。

F4 の送信に関して、これははっきり言って F4 の機能そのものを目的としている訳ではなく、本来なら不要です。ですが、どうもタブを開く Ctrl + T が効かない場合があるようで、その状態の場合、どうにか出来ないかと色々検証した結果、唯一 F4 のショートカットだけは効きました。これを実行した後なら、Ctrl + T でタブを開くことが出来ます。

詳しい原因は分かりませんが、For で回して ws.AppActivate w.LocationName するとなる模様です。致し方ないので既存ウィンドウに対して開く場合は、F4 を送信することにしました。

あと気を付ける点として、LocationName を使って最前面化しているので、もしエクスプローラーの設定が「タイトルバーに完全なパスを表示する」をオンにしていると、フォルダ名ではなくパスが入ってしまうので最前面化されません。その場合パスが格納されてるプロパティがありますので、そちらを使えば動作すると思われます。

楽天モバイルで DDNS 指定での VPN 接続が出来ない問題

大分更新していませんでしたが、備忘録のため更新します。

サボっていた間に色々ありまして、現在契約回線の一部に楽天モバイルがあります。このところ、契約回線の整理に向けて機種変更をして設定を行っていたのですが、正確に接続情報を入力しても楽天モバイルでは自宅の VPN に接続出来ないことが分かりました。

一応念の為とグローバル IP アドレスを直入力すると、なんと繋がりました。なのでその時点では DDNS サービス側の仕様・不具合かなと思いました。この症状は以下の記事と同様です。

https://toufulog.com/archives/542

上記ではルーターの IP タイプを IPv4 に変えると繋がるようになるとあります。私は Androidスマホですが、以下の APN 設定の項目を IPv4 のみに変えると接続出来るようになりました。めでたしめでたし。

APN 設定
APN 設定

今後のモバイルプラン

ここからは余談ですが、今日楽天モバイルの新プランが発表されましたね。音声回線を楽天モバイルに纏めようと思って、スマホの設定やら二段階認証の変更やらようやっとほぼ終わったところでのこのタイミング…。

0 円でなくなるのは想定済みでしたが、思ったより少し早かったのと、3GB / 1,078 円は自分にとって少しミスマッチです。過去のブログでも分かる通り、私は 0sim を使っていたくらいですし、基本的には容量もその程度で事足り、金額は 500 円以下が望ましいです。ですが色々あって入院することがあり、その間は逆に 2 桁 GB から無制限が欲しいという少し特殊な事情があります。なので段階制は嬉しいけど、普段のコストを抑えられたらなぁと思います。入院の頻度も多分、それ程でもないと思います。そもそも 1 年ちょっともう入院してないし。ただ菅さんの件以降、こうしたニーズにもあったプランが色々出てきたことは幸いでした。

このニュースを聞いて、povo と今使ってる LinksMate の組み合わせもアリかなぁと考えたりしています。普段は LinksMate でコストを抑え、povo は半年課金で回線維持、入院のときは povo の 20GB のトッピング、みたいな感じです。ただ横並びと揶揄される大手キャリアですので、楽天モバイルの改悪に追随して「povo 3.0」最低料金 1,078 円から!みたいなことも当然考えられます。

そんなこともあり、楽天モバイルは既存契約者なので 10 月までは実質で 0 円ですし、暫く様子見したいと思います。ランニングコストが 1,078 円とはいえ Rakuten Link が使えるのは魅力ですし、妥協してそのまま楽天モバイルのままの可能性も十分あり、移行するかは半々な感じです。以前のアイデアでは y.u mobile で普段は容量を使わず、サービスの特徴である 100GB までの無期限繰り越しを使って入院時に消化というのもありましたが、MNO でも選択肢が増えた今では採用することはなさそうです。

LINE Payカード改悪

LINE Payカードの2%還元率が5月末に終了ということで、話題になってます。奇しくもLINE Payカードを申し込んで先日届いたばかりです…。LINE Payカードで医療費が払えるなら良いなと思ってたのですが、使えたとしてもこれじゃ申し込んだ意味がなくなってしまうな。

LINE Payカード

3月中に色々あれこれ手続きしている序にLINE Payも試してみようと手続きしたのですが、ここに来て改悪かぁ。。嘗ての漢方スタイルクラブカードのように継続的だったので、まだ続くかなと思ってたのですが、残念ですね。

コード支払いをメインに「LINE Pay」のサービスのご利用全体を対象にしたものとなる予定、とのことですが、その条件次第では今後も魅力的かもしれません。だけど、
「LINE Pay」の各種サービスのご利用度合いによって最大2%、とアナウンスしているように、最大2%であってそれを達成するのは難しく、結局P-one Wizでいいじゃんってなりそう。。ただP-one Wizもいつまで1.5%が続くか分かりませんけどね。

2,000円チャージで1,000ポイントのキャンペーンで既にチャージしてしまったので、これらを6月までに消化しつつどんなプログラムになるか様子見しよう。

VBAのIsObjectとVBSのIsObject【備忘録】

VBAのソースをVBSに移植していて躓いたことがあったので、備忘録として残しておこうと思います。

VBAではNot elm Is Nothing

IE操作にてあるページでもしあるボタンがあったらクリック、といったスクリプトを書いていました。ボタンがないのにクリックをしてしまうと、オブジェクトがない、とエラーが出てしまいますので、IF文でオブジェクト(DOM要素)があるかチェックして、あったらクリックをします。しかしVBAでIsObjectでチェックすると、要素がないのにも関わらずTrueが返ってしまい、クリックする箇所で当然エラーが出ます。ですので、VBAではIF文をNot elm Is Nothingとしました。これで問題なく動作してました。

VBSではIsObject(elm)

VBSに移植すると、Not elm Is Nothingだと、オブジェクトがない、とエラーが出ます。ですのでIsObjectにしてみると、VBAでは要素がなくてもTrueが返っていたのにVBSではFalseが返り、IsObjectで問題なく動作しました。

原因について

これは、無いはずのDOM要素がSet elm = Nothingとして扱われているのかどうかで変わっているのかな、と思いました。VBAでは、DOM要素そのものがなくてもNothingで初期化された状態なので、IsObjectでTrueが返る。でもDOM要素そのものはないのでクリックするとエラーが出る。そしてNothingなので、Not elm Is NothingだとIF文には入らない。

VBSではNothingで初期化されていないので、Not elm Is Nothingでエラーが出る。同じくNothingで初期化されていないので、IsObjectでFalseが返る、といった感じなのかな。

VBSでは変数のデータ型を省いたり、そういった仕様の違いがあるのは知ってましたが、上記のような違いがあるのは初めて知りました。


Excel VBAでIEを思いのままに操作できるプログラミング術 Excel 2013/2010/2007/2003対応

Excel VBAでIEを思いのままに操作できるプログラミング術 Excel 2013/2010/2007/2003対応

骨折して1ヶ月3週

大分改善されて、日常生活はほぼ問題なくなりました。ただ完治はしておらず、ふと何らかの動作で痛みを感じることがあります。手首の痣は消えたけど未だに手のひらの痣は残ってます。でも大分消えかかってて、あと1〜2週したら消えそう。

あと寝返りを打つ時くらいになりましたが、未だに胸と腹の中間あたりが痛みます。痛みとして感じる回数としては手よりもこっちの方が多いと思います。寝返りをしたら確実に痛むので。

今日レントゲンを見ましたが、まだ亀裂がはっきり見えて素人目にはあまり改善していないように見えます。しかし実感としては以前出来なかった様々な動作が出来るようになって確実に改善はしているので、組織は出来てきているのでしょうね。また1ヶ月後くらいの通院で最後になるようです。

1ヶ月くらいで治るものだと思ってたけど、思ったより長引いています。