使用 'WlanScan' 刷新 WiFi 网络列表(将 api 语法从 c# 转换为 vba... 或解决方法?)
Refresh WiFi network list with 'WlanScan' (convert api syntax from c# to vba... or a workaround?)
我需要刷新 Window 的无线网络列表。
我很乐意接受我可以直接或间接来自VBA 的任何解决方法(cmdline、wmi 等)。 (我正在使用 Windows 7 Home 64 位和 Office 365 Pro 64 位。)
我可以通过多种方式以编程方式列出网络,包括 netsh
或下面的代码,但是 列表不会刷新 除非我实际单击 任务栏通知区域上的网络连接图标。
- 该列表不会如某些文档所述每 60 秒自动更新一次。
- 断开+重新连接 NIC 不是 feasible/sustainable 选项。
我想我 没有根据需要从 WlanOpenHandle 获得句柄 ,我对将 C 转换为 VBA 很陌生。
没有错误,但 WlanScan return 是未知代码 1168
。
- 我在这里改编自 VB 版本:wlanscan (wlanapi)
- MSDN 文档:WlanScan function
Related bits:
这是 VB
、 的 函数声明改编:
Public Shared Function WlanScan(ByVal hClientHandle As IntPtr, _
ByRef pInterfaceGuid As Guid, ByVal pDot11Ssid As IntPtr, _
ByVal pIeData As IntPtr, ByVal pReserved As IntPtr) As UInteger
End Function
...以及 C#
中 函数用法的示例:
Guid g;
//wlanHndl is the handle returned previously by calling [WlanOpenHandle]
for (int i = 0; i < infoList.dwNumberOfItems; i++)
{
g = infoList.InterfaceInfo[i].InterfaceGuid;
uint resultCode=WlanScan(wlanHndl, ref g, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero);
if (resultCode != 0)
return;
}
...和如何打开手柄,在C++
中(来自here):
dwResult = WlanOpenHandle(dwMaxClient, NULL, &dwCurVersion, &hClient);
if (dwResult != ERROR_SUCCESS) {
wprintf(L"WlanOpenHandle failed with error: %u\n", dwResult);
return 1;
// You can use FormatMessage here to find out why the function failed
}
"Un-hidden:"
Obtain (cached) list of wireless networks:
列出网络的代码效果很好 - 除了不能自行刷新。 (之前我在解析 netsh wlan show networks mode=bssid
的文本输出时遇到了同样的问题。)
我之前删除了这一部分,因为它很长而且除了刷新之外似乎工作正常。 -)
Option Explicit 'section's source: vbforums.com/showthread.php?632731
Private Const DOT11_SSID_MAX_LENGTH As Long = 32
Private Const WLAN_MAX_PHY_TYPE_NUMBER As Long = 8
Private Const WLAN_AVAILABLE_NETWORK_CONNECTED As Long = 1
Private Const WLAN_AVAILABLE_NETWORK_HAS_PROFILE As Long = 2
Private Type GUID 'from cpearson.com
Data1 As Long: Data2 As Integer: Data3 As Integer: Data4(7) As Byte
End Type
Private Type WLAN_INTERFACE_INFO
ifGuid As GUID: InterfaceDescription(255) As Byte: IsState As Long
End Type
Private Type DOT11_SSID
uSSIDLength As Long: ucSSID(DOT11_SSID_MAX_LENGTH - 1) As Byte
End Type
Private Type WLAN_AVAILABLE_NETWORK
strProfileName(511) As Byte: dot11Ssid As DOT11_SSID
dot11BssType As Long: uNumberOfBssids As Long
bNetworkConnectable As Long: wlanNotConnectableReason As Long
uNumberOfPhyTypes As Long: dot11PhyTypes(WLAN_MAX_PHY_TYPE_NUMBER - 1) As Long
bMorePhyTypes As Long: wlanSignalQuality As Long
bSEcurityEnabled As Long: dot11DefaultAuthAlgorithm As Long
dot11DefaultCipherAlgorithm As Long: dwflags As Long: dwReserved As Long
End Type
Private Type WLAN_INTERFACE_INFO_LIST
dwNumberOfItems As Long: dwIndex As Long: InterfaceInfo As WLAN_INTERFACE_INFO
End Type
Private Type WLAN_AVAILABLE_NETWORK_LIST
dwNumberOfItems As Long: dwIndex As Long: Network As WLAN_AVAILABLE_NETWORK
End Type
Declare PtrSafe Function WlanOpenHandle Lib "Wlanapi.dll" (ByVal dwClientVersion As Long, _
ByVal pdwReserved As Long, ByRef pdwNegotiaitedVersion As Long, _
ByRef phClientHandle As Long) As Long
Declare PtrSafe Function WlanEnumInterfaces Lib "Wlanapi.dll" (ByVal hClientHandle As Long, _
ByVal pReserved As Long, ppInterfaceList As Long) As Long
Declare PtrSafe Function WlanGetAvailableNetworkList Lib "Wlanapi.dll" ( _
ByVal hClientHandle As Long, pInterfaceGuid As GUID, ByVal dwflags As Long, _
ByVal pReserved As Long, ppAvailableNetworkList As Long) As Long
Declare PtrSafe Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (Destination As Any, _
Source As Any, ByVal Length As Long)
Declare PtrSafe Sub WlanFreeMemory Lib "Wlanapi.dll" (ByVal pMemory As Long)
Type WiFis
ssid As String: signal As Single
End Type
Public Function GetWiFi() As WiFis()
'returns an array of custom type WiFis (1st interface only)
Dim udtList As WLAN_INTERFACE_INFO_LIST, udtAvailList As WLAN_AVAILABLE_NETWORK_LIST, udtNetwork As WLAN_AVAILABLE_NETWORK
Dim lngReturn As Long, lngHandle As Long, lngVersion As Long, lngList As Long, lngAvailable As Long
Dim lngStart As Long, intCount As Integer, ssid As String, signal As Single, wifiOut() As WiFis
n = 0
lngReturn = WlanOpenHandle(2&, 0&, lngVersion, lngHandle) 'get handle
If lngReturn <> 0 Then
Debug.Print "Couldn't get wlan handle (Code " & lngReturn & ")"
Exit Function
End If
lngReturn = WlanEnumInterfaces(ByVal lngHandle, 0&, lngList) 'enumerate <*first interface only*>
CopyMemory udtList, ByVal lngList, Len(udtList)
lngReturn = WlanGetAvailableNetworkList(lngHandle, udtList.InterfaceInfo.ifGuid, 2&, 0&, lngAvailable) 'get network list
CopyMemory udtAvailList, ByVal lngAvailable, LenB(udtAvailList)
intCount = 0
lngStart = lngAvailable + 8
Do
CopyMemory udtNetwork, ByVal lngStart, Len(udtNetwork) ' Populate avail. network structure
ssid = Replace(StrConv(udtNetwork.dot11Ssid.ucSSID, vbUnicode), Chr(0), "")
If Len(ssid) < 4 Then ssid = "(Unnamed)"
signal = CSng(udtNetwork.wlanSignalQuality) / 100
'[Signal] = 0 to 100 which represents the signal strength (100 Signal)=(-100dBm RSSI), (100 Signal)=(-50dBm RSSI)
If udtNetwork.dwflags = 0 Then
n = n + 1
ReDim Preserve wifiOut(n)
wifiOut(n).ssid = ssid
wifiOut(n).signal = signal
Else
'skipping networks with [dwflags] > 0
'I *think* that's what I'm supposed to do
'Returns 3 for currently connected network, 2 for networks that have profiles
End If
intCount = intCount + 1
lngStart = lngStart + Len(udtNetwork)
Loop Until intCount = udtAvailList.dwNumberOfItems
WlanFreeMemory lngAvailable 'clean up memory
WlanFreeMemory lngList
GetWiFi = wifiOut 'Success! (function is populated with cached network list)
End Function
...and the problem:
Refresh network list using WlanScan
?
这不会产生VB错误,但是会return代码1168
(我无法识别)/(Source)
'Added blindly:'wlanui type library (wlanui.dll) and "wlan pref iua" (wlanconn.dll)
Public Type DOT11_SSID
uSSIDLength As LongPtr: ucSSID As String
End Type
Private Type GUID 'from cpearson.com/excel/CreateGUID.aspx
Data1 As LongPtr: Data2 As Integer
Data3 As Integer: Data4(0 To 7) As Byte
End Type
#If Win64 Then 'also new to Office-64bit, but seems okay
Declare PtrSafe Function WlanScan Lib "Wlanapi.dll" _
(ByVal hClientHandle As LongPtr, ByRef pInterfaceGuid As GUID, _
ByVal pDot11Ssid As LongPtr, ByVal pIeData As LongPtr, _
ByVal pReserved As LongPtr) As LongPtr
#Else
Private Declare WlanScan Lib "Wlanapi.dll" _
(ByVal hClientHandle As LongPtr, ByRef pInterfaceGuid As GUID, _
ByVal pDot11Ssid As LongPtr, ByVal pIeData As LongPtr, _
ByVal pReserved As LongPtr) As LongPtr
#End If
Sub test_RefreshNetworkList()
Dim hresult As LongPtr, phClientHandle As Long, pdwNegotiatedVersion As Long
Dim retVal As Longptr, g As GUID
hresult = WlanOpenHandle(2&, 0&, pdwNegotiatedVersion, phClientHandle)
retVal = WlanScan(phClientHandle, g, 0, 0, 0)
Select Case retVal
Case 87: Debug.Print "ERROR_INVALID_PARAMETER"
Case 6: Debug.Print "ERROR_INVALID_HANDLE"
Case 8: Debug.Print "ERROR_NOT_ENOUGH_MEMORY"
Case Else: Debug.Print "RPC_STATUS : " & retVal ' "misc errors"
End Select
End Sub
从 VBA 刷新网络列表肯定有一种迂回的方式?我对可以自动化的变通办法很满意……什么?!
谢谢!
Edit:
我在适用的(我认为)位置将 Long
更改为 LongPtr
。同样的错误。
这是 WlanOpenHandle
和 WlanScan
的定义。
Declare PtrSafe Function WlanOpenHandle Lib "Wlanapi.dll"
(ByVal dwClientVersion As LongPtr, _
ByVal pdwReserved As LongPtr,
ByRef pdwNegotiaitedVersion As LongPtr, _
ByRef phClientHandle As LongPtr ) As LongPtr
(...这也是我第一次尝试使用编译器常量。)
#If Win64 Then
Declare PtrSafe Function WlanScan Lib "Wlanapi.dll" _
(ByVal hClientHandle As LongPtr,
ByRef pInterfaceGuid As GUID, _
ByVal pDot11Ssid As LongPtr,
ByVal pIeData As LongPtr, _
ByVal pReserved As LongPtr) As LongPtr
#Else
Private Declare WlanScan Lib "Wlanapi.dll" _
(ByVal hClientHandle As LongPtr,
ByRef pInterfaceGuid As GUID, _
ByVal pDot11Ssid As LongPtr,
ByVal pIeData As LongPtr, _
ByVal pReserved As LongPtr ) As LongPtr
#End If
关于这些评论:
the list does not refresh unless I physically click the Network Connection icon
和
Surely there's a roundabout way to refresh the network list from VBA?
I'm cool with workarounds that I can automate... anything?!
这里有一个迂回的方法:以编程方式单击网络连接图标:
Sub ClickIt()
With CreateObject("WScript.Shell")
.Run "%windir%\explorer.exe ms-availablenetworks:"
End With
End Sub
您 'could' 在 application.wait 之后用 mouse_event 关闭它,因为它需要一些时间来刷新
我认为不刷新的主要问题是您永远不会关闭打开的句柄。这可能会导致问题,因为据我所知不应该有多个打开的句柄。
您使用 WlanOpenHandle
获得接口的句柄,但在完成它并获得所需信息后,您应该调用 WlanCloseHandle
关闭该句柄和关联的连接。
Declare PtrSafe Function WlanCloseHandle Lib "Wlanapi.dll" ( _
ByVal hClientHandle As LongPtr, _
Optional ByVal pReserved As LongPtr) As Long
然后在函数结束时:
WlanCloseHandle lngHandle 'Close handle
GetWiFi = wifiOut 'Success! (function is populated with cached network list)
End Function
任何错误处理程序,如果您要添加一个,都应该测试句柄是否不为 0,如果不是,则将其关闭。
我还更改了各种小东西,例如使用 LongPtr
作为指针以使您的代码兼容 64 位(注意:它 不 VBA6 兼容,这需要很多条件编译),修改你的声明以不使用可选参数,以及其他一些小东西。
我在一台设备上对以下代码进行了 10 次迭代测试,得到了 10 个不同的结果:
代码:
Public Function GetWiFi() As wifis()
'returns an array of custom type WiFis (1st interface only)
Dim udtList As WLAN_INTERFACE_INFO_LIST, udtAvailList As WLAN_AVAILABLE_NETWORK_LIST, udtNetwork As WLAN_AVAILABLE_NETWORK
Dim lngReturn As Long, pHandle As LongPtr, lngVersion As Long, pList As LongPtr, pAvailable As LongPtr
Dim pStart As LongPtr, intCount As Integer, ssid As String, signal As Single, wifiOut() As wifis
Dim n As Long
n = 0
lngReturn = WlanOpenHandle(2&, 0&, lngVersion, pHandle) 'get handle
If lngReturn <> 0 Then
Debug.Print "Couldn't get wlan handle (Code " & lngReturn & ")"
Exit Function
End If
lngReturn = WlanEnumInterfaces(ByVal pHandle, 0&, pList) 'enumerate <*first interface only*>
CopyMemory udtList, ByVal pList, Len(udtList)
lngReturn = WlanScan(pHandle, udtList.InterfaceInfo.ifGuid)
lngReturn = WlanGetAvailableNetworkList(pHandle, udtList.InterfaceInfo.ifGuid, 2&, 0&, pAvailable) 'get network list
CopyMemory udtAvailList, ByVal pAvailable, LenB(udtAvailList)
intCount = 0
pStart = pAvailable + 8
Do
CopyMemory udtNetwork, ByVal pStart, Len(udtNetwork) ' Populate avail. network structure
ssid = Replace(StrConv(udtNetwork.dot11Ssid.ucSSID, vbUnicode), Chr(0), "")
If Len(ssid) < 4 Then ssid = "(Unnamed)"
signal = CSng(udtNetwork.wlanSignalQuality) / 100
'[Signal] = 0 to 100 which represents the signal strength (100 Signal)=(-100dBm RSSI), (100 Signal)=(-50dBm RSSI)
If udtNetwork.dwflags = 0 Then
n = n + 1
ReDim Preserve wifiOut(n)
wifiOut(n).ssid = ssid
wifiOut(n).signal = signal
Else
'skipping networks with [dwflags] > 0
'I *think* that's what I'm supposed to do
'Returns 3 for currently connected network, 2 for networks that have profiles
End If
intCount = intCount + 1
pStart = pStart + Len(udtNetwork)
Loop Until intCount = udtAvailList.dwNumberOfItems
WlanFreeMemory pAvailable 'clean up memory
WlanFreeMemory pList
WlanCloseHandle pHandle 'Close handle
GetWiFi = wifiOut 'Success! (function is populated with cached network list)
End Function
类型和常量:
Private Const DOT11_SSID_MAX_LENGTH As Long = 32
Private Const WLAN_MAX_PHY_TYPE_NUMBER As Long = 8
Private Const WLAN_AVAILABLE_NETWORK_CONNECTED As Long = 1
Private Const WLAN_AVAILABLE_NETWORK_HAS_PROFILE As Long = 2
Public Type GUID
Data(15) As Byte
End Type
Private Type WLAN_INTERFACE_INFO
ifGuid As GUID: InterfaceDescription(255) As Byte: IsState As Long
End Type
Private Type DOT11_SSID
uSSIDLength As Long: ucSSID(DOT11_SSID_MAX_LENGTH - 1) As Byte
End Type
Private Type WLAN_AVAILABLE_NETWORK
strProfileName(511) As Byte: dot11Ssid As DOT11_SSID
dot11BssType As Long: uNumberOfBssids As Long
bNetworkConnectable As Long: wlanNotConnectableReason As Long
uNumberOfPhyTypes As Long: dot11PhyTypes(WLAN_MAX_PHY_TYPE_NUMBER - 1) As Long
bMorePhyTypes As Long: wlanSignalQuality As Long
bSEcurityEnabled As Long: dot11DefaultAuthAlgorithm As Long
dot11DefaultCipherAlgorithm As Long: dwflags As Long: dwReserved As Long
End Type
Private Type WLAN_INTERFACE_INFO_LIST
dwNumberOfItems As Long: dwIndex As Long: InterfaceInfo As WLAN_INTERFACE_INFO
End Type
Private Type WLAN_AVAILABLE_NETWORK_LIST
dwNumberOfItems As Long: dwIndex As Long: Network As WLAN_AVAILABLE_NETWORK
End Type
Public Type WiFis
ssid As String: signal As Single
End Type
函数声明:
Declare PtrSafe Function WlanOpenHandle Lib "Wlanapi.dll" (ByVal dwClientVersion As Long, _
ByVal pdwReserved As LongPtr, ByRef pdwNegotiaitedVersion As Long, _
ByRef phClientHandle As LongPtr) As Long
Declare PtrSafe Function WlanEnumInterfaces Lib "Wlanapi.dll" (ByVal hClientHandle As LongPtr, _
ByVal pReserved As LongPtr, ByRef ppInterfaceList As LongPtr) As Long
Declare PtrSafe Function WlanGetAvailableNetworkList Lib "Wlanapi.dll" ( _
ByVal hClientHandle As LongPtr, ByRef pInterfaceGuid As GUID, ByVal dwflags As Long, _
ByVal pReserved As LongPtr, ByRef ppAvailableNetworkList As LongPtr) As Long
Declare PtrSafe Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (Destination As Any, _
Source As Any, ByVal Length As Long)
Declare PtrSafe Function WlanScan Lib "Wlanapi.dll" _
(ByVal hClientHandle As LongPtr, ByRef pInterfaceGuid As GUID, _
Optional ByVal pDot11Ssid As LongPtr, Optional ByVal pIeData As LongPtr, _
Optional ByVal pReserved As LongPtr) As Long
Declare PtrSafe Function WlanCloseHandle Lib "Wlanapi.dll" ( _
ByVal hClientHandle As LongPtr, _
Optional ByVal pReserved As LongPtr) As Long
Declare PtrSafe Sub WlanFreeMemory Lib "Wlanapi.dll" (ByVal pMemory As LongPtr)
打印列表的测试调用:
Public Sub PrintWifis()
Dim aWifis() As wifis
aWifis = GetWiFi
Dim l As Long
For l = LBound(aWifis) To UBound(aWifis)
Debug.Print aWifis(l).ssid; aWifis(l).signal
Next
End Sub
这个项目成为了一项任务,因为它看起来 直截了当,好几次。我的第一次尝试捕获了 netsh wlan show networks mode=bssid
的输出,但我无法将列表获取到 刷新 。如果我改用 API 方法 (WlanScan
+WlanGetAvailableNetworkList
),我认为刷新会很容易,我从头开始,然后才意识到我 仍然 无法刷新数据。
在 post 完成这个问题后,EvR 的 eventually/finally led me to the ability to open/close the Network Connections list in the Windows notification area, which refreshed the cached text, so I re-wrote the process a third time, back to using netsh
. I finally got attempt #3 working (below), and then saw Erik's 完成了相同的结果...但要少得多 "hacky",而且速度快 25 倍。
所以,我显然会选择 "final attempt #4",但我想无论如何我都会 post 这个替代答案,因为一些概念可以很容易地转移到其他问题上 hack fix 是必需的。
Option Compare Binary
Option Explicit
Public Declare Function ShellExecute Lib "Shell32.dll" Alias "ShellExecuteA" (ByVal hWnd _
As LongPtr, ByVal lpOperation As String, ByVal lpFile As String, ByVal lpParameters _
As String, ByVal lpDirectory As String, ByVal nShowCmd As LongPtr) As LongPtr
Public Declare Function GetWindowText Lib "User32.dll" Alias "GetWindowTextA" _
(ByVal hWnd As LongPtr, ByVal lpString As String, ByVal cch As LongPtr) As LongPtr
Public Declare Function GetForegroundWindow Lib "User32.dll" () As LongPtr
Type WiFis
ssid As String 'wifi network name
signal As Single 'wifi signal strength%
End Type
Private Function IsNetworksWindow(hWnd As Long, nCaption As String) As Boolean
'returns TRUE if the window caption (title) of window [hWnd]=[nCaption]
Dim title As String * 255
GetWindowText hWnd, title, 255 'get window caption
IsNetworksWindow = (nCaption = Left$(title, Len(nCaption)))
End Function
Sub RefreshWifiList()
'open "available networks" window (to refresh cached network list)
Const clsID = "shell:::{38A98528-6CBF-4CA9-8DC0-B1E1D10F7B1B}" 'clsid of req'd window
Const nCaption = "View Available Networks" 'title of req'd window
Dim retVal As LongPtr
retVal = ShellExecute(0, "open", clsID, "", "", 0) 'open clsID
If retVal < 33 Then Stop 'Error. Full list here: [http://oehive.org/node/528]
Do
DoEvents: Loop While Not IsNetworksWindow(GetForegroundWindow, nCaption) 'wait for refresh
ThisWorkbook.Activate: AppActivate Application.Caption 'return to Excel
End Sub
Public Function getCmdLineOutput(cmd As String) As String
'run cmdline in hidden window and return string of output
Dim tmpFile As String: tmpFile = Environ("temp") & "\cmd_out.tmp" 'create tmp file
If Dir(tmpFile) <> "" Then Kill tmpFile 'delete tmp file
With CreateObject("WScript.Shell") 'run cmdline command
.Run "cmd /c """ & cmd & """ > " & tmpFile, 0, True '0=Hide Window
End With
With CreateObject("Scripting.FileSystemObject") 'open fso
getCmdLineOutput = Trim(.opentextfile(tmpFile).ReadAll()) 'read temp file
.DeleteFile tmpFile 'delete temp file
End With
End Function
Public Function GetWiFi() As WiFis()
'extract [ssid]'s & [signal]'s from list to populate array of networks
Dim stNet As String, pStart As Long, pStop As Long: pStop = 1
Dim ssid As String, signal As String, wiFi() As WiFis: ReDim wiFi(0 To 0)
Application.ScreenUpdating = False
RefreshWifiList 'refresh wifi list
stNet = getCmdLineOutput("netsh wlan show networks mode=bssid") 'get network list
stNet = Mid$(stNet, InStr(stNet, "SSID")) 'trim extraneous chars
stNet = Replace(Replace(Replace(stNet, " ", ""), vbCrLf, ""), vbLf & vbLf, vbLf)
Do While InStr(pStop, stNet, "SSID") > 0
pStart = InStr(InStr(pStop, stNet, "SSID"), stNet, ":") + 1 'find ssid start
pStop = InStr(pStart, stNet, "Networktype") 'find ssid stop
ssid = Mid$(stNet, pStart, pStop - pStart) 'extract ssid
pStart = InStr(pStop, stNet, "Signal:") + 7 'find signal start
pStop = InStr(pStart, stNet, "%") 'find signal stop
signal = CSng(Mid$(stNet, pStart, pStop - pStart)) / 100 'extract signal
If signal = 0 Then Stop: If ssid = "" Then ssid = "(Unnamed)" 'validate
ReDim Preserve wiFi(UBound(wiFi) + 1) 'populate array
wiFi(UBound(wiFi)).ssid = ssid: wiFi(UBound(wiFi)).signal = signal
Loop
GetWiFi = wiFi
End Function
Sub demo()
Dim wiFi() As WiFis, netNum As Long
wiFi() = GetWiFi() 'populate array of networks
For netNum = 1 To UBound(wiFi) 'loop through networks
With wiFi(netNum)
Debug.Print .ssid, Format(.signal, "0%") 'print ssid & signal
End With
Next netNum
End Sub
Sub timeTest_listNetworks()
Dim wiFi() As WiFis, netNum As Long, n As Long
Dim startTime As Single, allTime As Single: allTime = Timer
For n = 1 To 5 'repeat test 5x
Erase wiFi() 'clear array
startTime = Timer
wiFi() = GetWiFi() 'refresh array of networks
For netNum = 1 To UBound(wiFi) 'loop through networks
Debug.Print wiFi(netNum).ssid & "=" & Format(wiFi(netNum).signal, "0%") & " ";
Next netNum
Debug.Print "(" & Round(Timer - startTime, 1) & " sec)"
Next n
Debug.Print "Total: " & Round(Timer - allTime, 1) & " sec"
End Sub
更多信息:
- 杰夫查佩尔:
Explorer
Command Line
- MS 文档:
ShellExecuteA
Function
- OpenEdge :
ShellExecute
return codes
- MS 文档:
GetWindowTextA
Function
- MS 文档:
GetForegroundWindow
Function
- SS64:
WScript.Shell.Run
Method
- MS 文档:Scripting
FileSystemObject.OpenTextFile
Method
- MS 文档:
AppActivate
Statement
- 堆栈溢出:Convert Signal Strength% to RSSI dBm
我需要刷新 Window 的无线网络列表。
我很乐意接受我可以直接或间接来自VBA 的任何解决方法(cmdline、wmi 等)。 (我正在使用 Windows 7 Home 64 位和 Office 365 Pro 64 位。)
我可以通过多种方式以编程方式列出网络,包括 netsh
或下面的代码,但是 列表不会刷新 除非我实际单击
- 该列表不会如某些文档所述每 60 秒自动更新一次。
- 断开+重新连接 NIC 不是 feasible/sustainable 选项。
我想我 没有根据需要从 WlanOpenHandle 获得句柄 ,我对将 C 转换为 VBA 很陌生。
没有错误,但 WlanScan return 是未知代码 1168
。
- 我在这里改编自 VB 版本:wlanscan (wlanapi)
- MSDN 文档:WlanScan function
Related bits:
这是 VB
、 的 函数声明改编:
Public Shared Function WlanScan(ByVal hClientHandle As IntPtr, _
ByRef pInterfaceGuid As Guid, ByVal pDot11Ssid As IntPtr, _
ByVal pIeData As IntPtr, ByVal pReserved As IntPtr) As UInteger
End Function
...以及 C#
中 函数用法的示例:
Guid g;
//wlanHndl is the handle returned previously by calling [WlanOpenHandle]
for (int i = 0; i < infoList.dwNumberOfItems; i++)
{
g = infoList.InterfaceInfo[i].InterfaceGuid;
uint resultCode=WlanScan(wlanHndl, ref g, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero);
if (resultCode != 0)
return;
}
...和如何打开手柄,在C++
中(来自here):
dwResult = WlanOpenHandle(dwMaxClient, NULL, &dwCurVersion, &hClient);
if (dwResult != ERROR_SUCCESS) {
wprintf(L"WlanOpenHandle failed with error: %u\n", dwResult);
return 1;
// You can use FormatMessage here to find out why the function failed
}
"Un-hidden:"
Obtain (cached) list of wireless networks:
列出网络的代码效果很好 - 除了不能自行刷新。 (之前我在解析 netsh wlan show networks mode=bssid
的文本输出时遇到了同样的问题。)
我之前删除了这一部分,因为它很长而且除了刷新之外似乎工作正常。 -)
Option Explicit 'section's source: vbforums.com/showthread.php?632731
Private Const DOT11_SSID_MAX_LENGTH As Long = 32
Private Const WLAN_MAX_PHY_TYPE_NUMBER As Long = 8
Private Const WLAN_AVAILABLE_NETWORK_CONNECTED As Long = 1
Private Const WLAN_AVAILABLE_NETWORK_HAS_PROFILE As Long = 2
Private Type GUID 'from cpearson.com
Data1 As Long: Data2 As Integer: Data3 As Integer: Data4(7) As Byte
End Type
Private Type WLAN_INTERFACE_INFO
ifGuid As GUID: InterfaceDescription(255) As Byte: IsState As Long
End Type
Private Type DOT11_SSID
uSSIDLength As Long: ucSSID(DOT11_SSID_MAX_LENGTH - 1) As Byte
End Type
Private Type WLAN_AVAILABLE_NETWORK
strProfileName(511) As Byte: dot11Ssid As DOT11_SSID
dot11BssType As Long: uNumberOfBssids As Long
bNetworkConnectable As Long: wlanNotConnectableReason As Long
uNumberOfPhyTypes As Long: dot11PhyTypes(WLAN_MAX_PHY_TYPE_NUMBER - 1) As Long
bMorePhyTypes As Long: wlanSignalQuality As Long
bSEcurityEnabled As Long: dot11DefaultAuthAlgorithm As Long
dot11DefaultCipherAlgorithm As Long: dwflags As Long: dwReserved As Long
End Type
Private Type WLAN_INTERFACE_INFO_LIST
dwNumberOfItems As Long: dwIndex As Long: InterfaceInfo As WLAN_INTERFACE_INFO
End Type
Private Type WLAN_AVAILABLE_NETWORK_LIST
dwNumberOfItems As Long: dwIndex As Long: Network As WLAN_AVAILABLE_NETWORK
End Type
Declare PtrSafe Function WlanOpenHandle Lib "Wlanapi.dll" (ByVal dwClientVersion As Long, _
ByVal pdwReserved As Long, ByRef pdwNegotiaitedVersion As Long, _
ByRef phClientHandle As Long) As Long
Declare PtrSafe Function WlanEnumInterfaces Lib "Wlanapi.dll" (ByVal hClientHandle As Long, _
ByVal pReserved As Long, ppInterfaceList As Long) As Long
Declare PtrSafe Function WlanGetAvailableNetworkList Lib "Wlanapi.dll" ( _
ByVal hClientHandle As Long, pInterfaceGuid As GUID, ByVal dwflags As Long, _
ByVal pReserved As Long, ppAvailableNetworkList As Long) As Long
Declare PtrSafe Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (Destination As Any, _
Source As Any, ByVal Length As Long)
Declare PtrSafe Sub WlanFreeMemory Lib "Wlanapi.dll" (ByVal pMemory As Long)
Type WiFis
ssid As String: signal As Single
End Type
Public Function GetWiFi() As WiFis()
'returns an array of custom type WiFis (1st interface only)
Dim udtList As WLAN_INTERFACE_INFO_LIST, udtAvailList As WLAN_AVAILABLE_NETWORK_LIST, udtNetwork As WLAN_AVAILABLE_NETWORK
Dim lngReturn As Long, lngHandle As Long, lngVersion As Long, lngList As Long, lngAvailable As Long
Dim lngStart As Long, intCount As Integer, ssid As String, signal As Single, wifiOut() As WiFis
n = 0
lngReturn = WlanOpenHandle(2&, 0&, lngVersion, lngHandle) 'get handle
If lngReturn <> 0 Then
Debug.Print "Couldn't get wlan handle (Code " & lngReturn & ")"
Exit Function
End If
lngReturn = WlanEnumInterfaces(ByVal lngHandle, 0&, lngList) 'enumerate <*first interface only*>
CopyMemory udtList, ByVal lngList, Len(udtList)
lngReturn = WlanGetAvailableNetworkList(lngHandle, udtList.InterfaceInfo.ifGuid, 2&, 0&, lngAvailable) 'get network list
CopyMemory udtAvailList, ByVal lngAvailable, LenB(udtAvailList)
intCount = 0
lngStart = lngAvailable + 8
Do
CopyMemory udtNetwork, ByVal lngStart, Len(udtNetwork) ' Populate avail. network structure
ssid = Replace(StrConv(udtNetwork.dot11Ssid.ucSSID, vbUnicode), Chr(0), "")
If Len(ssid) < 4 Then ssid = "(Unnamed)"
signal = CSng(udtNetwork.wlanSignalQuality) / 100
'[Signal] = 0 to 100 which represents the signal strength (100 Signal)=(-100dBm RSSI), (100 Signal)=(-50dBm RSSI)
If udtNetwork.dwflags = 0 Then
n = n + 1
ReDim Preserve wifiOut(n)
wifiOut(n).ssid = ssid
wifiOut(n).signal = signal
Else
'skipping networks with [dwflags] > 0
'I *think* that's what I'm supposed to do
'Returns 3 for currently connected network, 2 for networks that have profiles
End If
intCount = intCount + 1
lngStart = lngStart + Len(udtNetwork)
Loop Until intCount = udtAvailList.dwNumberOfItems
WlanFreeMemory lngAvailable 'clean up memory
WlanFreeMemory lngList
GetWiFi = wifiOut 'Success! (function is populated with cached network list)
End Function
...and the problem:
Refresh network list using
WlanScan
?
这不会产生VB错误,但是会return代码1168
(我无法识别)/(Source)
'Added blindly:'wlanui type library (wlanui.dll) and "wlan pref iua" (wlanconn.dll)
Public Type DOT11_SSID
uSSIDLength As LongPtr: ucSSID As String
End Type
Private Type GUID 'from cpearson.com/excel/CreateGUID.aspx
Data1 As LongPtr: Data2 As Integer
Data3 As Integer: Data4(0 To 7) As Byte
End Type
#If Win64 Then 'also new to Office-64bit, but seems okay
Declare PtrSafe Function WlanScan Lib "Wlanapi.dll" _
(ByVal hClientHandle As LongPtr, ByRef pInterfaceGuid As GUID, _
ByVal pDot11Ssid As LongPtr, ByVal pIeData As LongPtr, _
ByVal pReserved As LongPtr) As LongPtr
#Else
Private Declare WlanScan Lib "Wlanapi.dll" _
(ByVal hClientHandle As LongPtr, ByRef pInterfaceGuid As GUID, _
ByVal pDot11Ssid As LongPtr, ByVal pIeData As LongPtr, _
ByVal pReserved As LongPtr) As LongPtr
#End If
Sub test_RefreshNetworkList()
Dim hresult As LongPtr, phClientHandle As Long, pdwNegotiatedVersion As Long
Dim retVal As Longptr, g As GUID
hresult = WlanOpenHandle(2&, 0&, pdwNegotiatedVersion, phClientHandle)
retVal = WlanScan(phClientHandle, g, 0, 0, 0)
Select Case retVal
Case 87: Debug.Print "ERROR_INVALID_PARAMETER"
Case 6: Debug.Print "ERROR_INVALID_HANDLE"
Case 8: Debug.Print "ERROR_NOT_ENOUGH_MEMORY"
Case Else: Debug.Print "RPC_STATUS : " & retVal ' "misc errors"
End Select
End Sub
从 VBA 刷新网络列表肯定有一种迂回的方式?我对可以自动化的变通办法很满意……什么?!
Edit:
我在适用的(我认为)位置将 Long
更改为 LongPtr
。同样的错误。
这是 WlanOpenHandle
和 WlanScan
的定义。
Declare PtrSafe Function WlanOpenHandle Lib "Wlanapi.dll"
(ByVal dwClientVersion As LongPtr, _
ByVal pdwReserved As LongPtr,
ByRef pdwNegotiaitedVersion As LongPtr, _
ByRef phClientHandle As LongPtr ) As LongPtr
(...这也是我第一次尝试使用编译器常量。)
#If Win64 Then
Declare PtrSafe Function WlanScan Lib "Wlanapi.dll" _
(ByVal hClientHandle As LongPtr,
ByRef pInterfaceGuid As GUID, _
ByVal pDot11Ssid As LongPtr,
ByVal pIeData As LongPtr, _
ByVal pReserved As LongPtr) As LongPtr
#Else
Private Declare WlanScan Lib "Wlanapi.dll" _
(ByVal hClientHandle As LongPtr,
ByRef pInterfaceGuid As GUID, _
ByVal pDot11Ssid As LongPtr,
ByVal pIeData As LongPtr, _
ByVal pReserved As LongPtr ) As LongPtr
#End If
关于这些评论:
the list does not refresh unless I physically click the Network Connection icon
和
Surely there's a roundabout way to refresh the network list from VBA? I'm cool with workarounds that I can automate... anything?!
这里有一个迂回的方法:以编程方式单击网络连接图标:
Sub ClickIt()
With CreateObject("WScript.Shell")
.Run "%windir%\explorer.exe ms-availablenetworks:"
End With
End Sub
您 'could' 在 application.wait 之后用 mouse_event 关闭它,因为它需要一些时间来刷新
我认为不刷新的主要问题是您永远不会关闭打开的句柄。这可能会导致问题,因为据我所知不应该有多个打开的句柄。
您使用 WlanOpenHandle
获得接口的句柄,但在完成它并获得所需信息后,您应该调用 WlanCloseHandle
关闭该句柄和关联的连接。
Declare PtrSafe Function WlanCloseHandle Lib "Wlanapi.dll" ( _
ByVal hClientHandle As LongPtr, _
Optional ByVal pReserved As LongPtr) As Long
然后在函数结束时:
WlanCloseHandle lngHandle 'Close handle
GetWiFi = wifiOut 'Success! (function is populated with cached network list)
End Function
任何错误处理程序,如果您要添加一个,都应该测试句柄是否不为 0,如果不是,则将其关闭。
我还更改了各种小东西,例如使用 LongPtr
作为指针以使您的代码兼容 64 位(注意:它 不 VBA6 兼容,这需要很多条件编译),修改你的声明以不使用可选参数,以及其他一些小东西。
我在一台设备上对以下代码进行了 10 次迭代测试,得到了 10 个不同的结果:
代码:
Public Function GetWiFi() As wifis()
'returns an array of custom type WiFis (1st interface only)
Dim udtList As WLAN_INTERFACE_INFO_LIST, udtAvailList As WLAN_AVAILABLE_NETWORK_LIST, udtNetwork As WLAN_AVAILABLE_NETWORK
Dim lngReturn As Long, pHandle As LongPtr, lngVersion As Long, pList As LongPtr, pAvailable As LongPtr
Dim pStart As LongPtr, intCount As Integer, ssid As String, signal As Single, wifiOut() As wifis
Dim n As Long
n = 0
lngReturn = WlanOpenHandle(2&, 0&, lngVersion, pHandle) 'get handle
If lngReturn <> 0 Then
Debug.Print "Couldn't get wlan handle (Code " & lngReturn & ")"
Exit Function
End If
lngReturn = WlanEnumInterfaces(ByVal pHandle, 0&, pList) 'enumerate <*first interface only*>
CopyMemory udtList, ByVal pList, Len(udtList)
lngReturn = WlanScan(pHandle, udtList.InterfaceInfo.ifGuid)
lngReturn = WlanGetAvailableNetworkList(pHandle, udtList.InterfaceInfo.ifGuid, 2&, 0&, pAvailable) 'get network list
CopyMemory udtAvailList, ByVal pAvailable, LenB(udtAvailList)
intCount = 0
pStart = pAvailable + 8
Do
CopyMemory udtNetwork, ByVal pStart, Len(udtNetwork) ' Populate avail. network structure
ssid = Replace(StrConv(udtNetwork.dot11Ssid.ucSSID, vbUnicode), Chr(0), "")
If Len(ssid) < 4 Then ssid = "(Unnamed)"
signal = CSng(udtNetwork.wlanSignalQuality) / 100
'[Signal] = 0 to 100 which represents the signal strength (100 Signal)=(-100dBm RSSI), (100 Signal)=(-50dBm RSSI)
If udtNetwork.dwflags = 0 Then
n = n + 1
ReDim Preserve wifiOut(n)
wifiOut(n).ssid = ssid
wifiOut(n).signal = signal
Else
'skipping networks with [dwflags] > 0
'I *think* that's what I'm supposed to do
'Returns 3 for currently connected network, 2 for networks that have profiles
End If
intCount = intCount + 1
pStart = pStart + Len(udtNetwork)
Loop Until intCount = udtAvailList.dwNumberOfItems
WlanFreeMemory pAvailable 'clean up memory
WlanFreeMemory pList
WlanCloseHandle pHandle 'Close handle
GetWiFi = wifiOut 'Success! (function is populated with cached network list)
End Function
类型和常量:
Private Const DOT11_SSID_MAX_LENGTH As Long = 32
Private Const WLAN_MAX_PHY_TYPE_NUMBER As Long = 8
Private Const WLAN_AVAILABLE_NETWORK_CONNECTED As Long = 1
Private Const WLAN_AVAILABLE_NETWORK_HAS_PROFILE As Long = 2
Public Type GUID
Data(15) As Byte
End Type
Private Type WLAN_INTERFACE_INFO
ifGuid As GUID: InterfaceDescription(255) As Byte: IsState As Long
End Type
Private Type DOT11_SSID
uSSIDLength As Long: ucSSID(DOT11_SSID_MAX_LENGTH - 1) As Byte
End Type
Private Type WLAN_AVAILABLE_NETWORK
strProfileName(511) As Byte: dot11Ssid As DOT11_SSID
dot11BssType As Long: uNumberOfBssids As Long
bNetworkConnectable As Long: wlanNotConnectableReason As Long
uNumberOfPhyTypes As Long: dot11PhyTypes(WLAN_MAX_PHY_TYPE_NUMBER - 1) As Long
bMorePhyTypes As Long: wlanSignalQuality As Long
bSEcurityEnabled As Long: dot11DefaultAuthAlgorithm As Long
dot11DefaultCipherAlgorithm As Long: dwflags As Long: dwReserved As Long
End Type
Private Type WLAN_INTERFACE_INFO_LIST
dwNumberOfItems As Long: dwIndex As Long: InterfaceInfo As WLAN_INTERFACE_INFO
End Type
Private Type WLAN_AVAILABLE_NETWORK_LIST
dwNumberOfItems As Long: dwIndex As Long: Network As WLAN_AVAILABLE_NETWORK
End Type
Public Type WiFis
ssid As String: signal As Single
End Type
函数声明:
Declare PtrSafe Function WlanOpenHandle Lib "Wlanapi.dll" (ByVal dwClientVersion As Long, _
ByVal pdwReserved As LongPtr, ByRef pdwNegotiaitedVersion As Long, _
ByRef phClientHandle As LongPtr) As Long
Declare PtrSafe Function WlanEnumInterfaces Lib "Wlanapi.dll" (ByVal hClientHandle As LongPtr, _
ByVal pReserved As LongPtr, ByRef ppInterfaceList As LongPtr) As Long
Declare PtrSafe Function WlanGetAvailableNetworkList Lib "Wlanapi.dll" ( _
ByVal hClientHandle As LongPtr, ByRef pInterfaceGuid As GUID, ByVal dwflags As Long, _
ByVal pReserved As LongPtr, ByRef ppAvailableNetworkList As LongPtr) As Long
Declare PtrSafe Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (Destination As Any, _
Source As Any, ByVal Length As Long)
Declare PtrSafe Function WlanScan Lib "Wlanapi.dll" _
(ByVal hClientHandle As LongPtr, ByRef pInterfaceGuid As GUID, _
Optional ByVal pDot11Ssid As LongPtr, Optional ByVal pIeData As LongPtr, _
Optional ByVal pReserved As LongPtr) As Long
Declare PtrSafe Function WlanCloseHandle Lib "Wlanapi.dll" ( _
ByVal hClientHandle As LongPtr, _
Optional ByVal pReserved As LongPtr) As Long
Declare PtrSafe Sub WlanFreeMemory Lib "Wlanapi.dll" (ByVal pMemory As LongPtr)
打印列表的测试调用:
Public Sub PrintWifis()
Dim aWifis() As wifis
aWifis = GetWiFi
Dim l As Long
For l = LBound(aWifis) To UBound(aWifis)
Debug.Print aWifis(l).ssid; aWifis(l).signal
Next
End Sub
这个项目成为了一项任务,因为它看起来 直截了当,好几次。我的第一次尝试捕获了 netsh wlan show networks mode=bssid
的输出,但我无法将列表获取到 刷新 。如果我改用 API 方法 (WlanScan
+WlanGetAvailableNetworkList
),我认为刷新会很容易,我从头开始,然后才意识到我 仍然 无法刷新数据。
在 post 完成这个问题后,EvR 的 netsh
. I finally got attempt #3 working (below), and then saw Erik's
所以,我显然会选择 "final attempt #4",但我想无论如何我都会 post 这个替代答案,因为一些概念可以很容易地转移到其他问题上 hack fix 是必需的。
Option Compare Binary
Option Explicit
Public Declare Function ShellExecute Lib "Shell32.dll" Alias "ShellExecuteA" (ByVal hWnd _
As LongPtr, ByVal lpOperation As String, ByVal lpFile As String, ByVal lpParameters _
As String, ByVal lpDirectory As String, ByVal nShowCmd As LongPtr) As LongPtr
Public Declare Function GetWindowText Lib "User32.dll" Alias "GetWindowTextA" _
(ByVal hWnd As LongPtr, ByVal lpString As String, ByVal cch As LongPtr) As LongPtr
Public Declare Function GetForegroundWindow Lib "User32.dll" () As LongPtr
Type WiFis
ssid As String 'wifi network name
signal As Single 'wifi signal strength%
End Type
Private Function IsNetworksWindow(hWnd As Long, nCaption As String) As Boolean
'returns TRUE if the window caption (title) of window [hWnd]=[nCaption]
Dim title As String * 255
GetWindowText hWnd, title, 255 'get window caption
IsNetworksWindow = (nCaption = Left$(title, Len(nCaption)))
End Function
Sub RefreshWifiList()
'open "available networks" window (to refresh cached network list)
Const clsID = "shell:::{38A98528-6CBF-4CA9-8DC0-B1E1D10F7B1B}" 'clsid of req'd window
Const nCaption = "View Available Networks" 'title of req'd window
Dim retVal As LongPtr
retVal = ShellExecute(0, "open", clsID, "", "", 0) 'open clsID
If retVal < 33 Then Stop 'Error. Full list here: [http://oehive.org/node/528]
Do
DoEvents: Loop While Not IsNetworksWindow(GetForegroundWindow, nCaption) 'wait for refresh
ThisWorkbook.Activate: AppActivate Application.Caption 'return to Excel
End Sub
Public Function getCmdLineOutput(cmd As String) As String
'run cmdline in hidden window and return string of output
Dim tmpFile As String: tmpFile = Environ("temp") & "\cmd_out.tmp" 'create tmp file
If Dir(tmpFile) <> "" Then Kill tmpFile 'delete tmp file
With CreateObject("WScript.Shell") 'run cmdline command
.Run "cmd /c """ & cmd & """ > " & tmpFile, 0, True '0=Hide Window
End With
With CreateObject("Scripting.FileSystemObject") 'open fso
getCmdLineOutput = Trim(.opentextfile(tmpFile).ReadAll()) 'read temp file
.DeleteFile tmpFile 'delete temp file
End With
End Function
Public Function GetWiFi() As WiFis()
'extract [ssid]'s & [signal]'s from list to populate array of networks
Dim stNet As String, pStart As Long, pStop As Long: pStop = 1
Dim ssid As String, signal As String, wiFi() As WiFis: ReDim wiFi(0 To 0)
Application.ScreenUpdating = False
RefreshWifiList 'refresh wifi list
stNet = getCmdLineOutput("netsh wlan show networks mode=bssid") 'get network list
stNet = Mid$(stNet, InStr(stNet, "SSID")) 'trim extraneous chars
stNet = Replace(Replace(Replace(stNet, " ", ""), vbCrLf, ""), vbLf & vbLf, vbLf)
Do While InStr(pStop, stNet, "SSID") > 0
pStart = InStr(InStr(pStop, stNet, "SSID"), stNet, ":") + 1 'find ssid start
pStop = InStr(pStart, stNet, "Networktype") 'find ssid stop
ssid = Mid$(stNet, pStart, pStop - pStart) 'extract ssid
pStart = InStr(pStop, stNet, "Signal:") + 7 'find signal start
pStop = InStr(pStart, stNet, "%") 'find signal stop
signal = CSng(Mid$(stNet, pStart, pStop - pStart)) / 100 'extract signal
If signal = 0 Then Stop: If ssid = "" Then ssid = "(Unnamed)" 'validate
ReDim Preserve wiFi(UBound(wiFi) + 1) 'populate array
wiFi(UBound(wiFi)).ssid = ssid: wiFi(UBound(wiFi)).signal = signal
Loop
GetWiFi = wiFi
End Function
Sub demo()
Dim wiFi() As WiFis, netNum As Long
wiFi() = GetWiFi() 'populate array of networks
For netNum = 1 To UBound(wiFi) 'loop through networks
With wiFi(netNum)
Debug.Print .ssid, Format(.signal, "0%") 'print ssid & signal
End With
Next netNum
End Sub
Sub timeTest_listNetworks()
Dim wiFi() As WiFis, netNum As Long, n As Long
Dim startTime As Single, allTime As Single: allTime = Timer
For n = 1 To 5 'repeat test 5x
Erase wiFi() 'clear array
startTime = Timer
wiFi() = GetWiFi() 'refresh array of networks
For netNum = 1 To UBound(wiFi) 'loop through networks
Debug.Print wiFi(netNum).ssid & "=" & Format(wiFi(netNum).signal, "0%") & " ";
Next netNum
Debug.Print "(" & Round(Timer - startTime, 1) & " sec)"
Next n
Debug.Print "Total: " & Round(Timer - allTime, 1) & " sec"
End Sub
更多信息:
- 杰夫查佩尔:
Explorer
Command Line - MS 文档:
ShellExecuteA
Function - OpenEdge :
ShellExecute
return codes - MS 文档:
GetWindowTextA
Function - MS 文档:
GetForegroundWindow
Function - SS64:
WScript.Shell.Run
Method - MS 文档:Scripting
FileSystemObject.OpenTextFile
Method - MS 文档:
AppActivate
Statement - 堆栈溢出:Convert Signal Strength% to RSSI dBm