How to connect to WiFi network on Windows using Py

2019-07-10 08:05发布

问题:

I am trying to write a script in Python 3 but all modules available today work on python 2 which will enable me to search for wireless networks and to connect to them. Is there any Python 3 library for this?

The code I tried for python 2

from wireless import Wireless
wireless = Wireless()
wireless.connect(ssid='ssid', password='password')

which is giving me an error

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "C:\Users\Himanshu Poddar\AppData\Local\Programs\Python\Python36-32\lib\site-packages\wireless\Wireless.py", line 23, in __init__
    self._driver_name = self._detectDriver()
  File "C:\Users\Himanshu Poddar\AppData\Local\Programs\Python\Python36-32\lib\site-packages\wireless\Wireless.py", line 50, in _detectDriver
    compare = self.vercmp(ver, "0.9.9.0")
  File "C:\Users\Himanshu Poddar\AppData\Local\Programs\Python\Python36-32\lib\site-packages\wireless\Wireless.py", line 71, in vercmp
    return cmp(normalize(actual), normalize(test))
NameError: name 'cmp' is not defined

But this is not working since it is based on python 2. Is there any way to connect to a wifi using Python 3

回答1:

Notes (about [PyPI]: wireless 0.3.2]):

  • Does not (yet) support Python 3: as somewhere in the code it uses the cmp function (which is only available in Python 2)
    • I wanted to submit a pull request (as the fix is trivial), but apparently on GitHub it was already fixed, yet the PyPI repository was not updated (since 2016)
  • Does not work on Win (only Nix drivers are listed on the homepage - basically it only launches shell commands)

As a consequence, I'd suggest looking for alternatives:

  • One that I've encountered a couple of days ago is Win32Wifi: ([PyPI]: win32wifi 0.1.0, although this wasn't updated since 2017 either). Check [SO]: Unable to get all available networks using WlanGetAvailableNetworkList in Python (@CristiFati's answer) for more details

Alright, after a lot of browsing of:

  • Win32Wifi source code
  • Google:
    • [MS.Docs]: wlanapi.h header
    • [CodePlex.Archive]: managedwifi
    • [MS.Docs]: Netsh Commands for Wireless Local Area Network (WLAN) in Windows Server 2008
    • Many others

, I was able to come up with something.

code.py:

#!/usr/bin/env python3

import sys
import time
import ctypes
import comtypes
import traceback
from win32wifi import Win32Wifi as ww


ERROR_SUCCESS = 0

WLAN_CONNECTION_HIDDEN_NETWORK = 0x00000001


class WLANException(Exception): pass


class ConnectCallbackContext(ctypes.Structure):
    _fields_ = [
        ("guid", ctypes.c_wchar_p),
        ("start", ctypes.c_byte),
        ("end", ctypes.c_byte),
        ("fail", ctypes.c_byte),
    ]


def _wlan_connect_callback(data, context_addr):
    if context_addr:
        context = ConnectCallbackContext.from_address(context_addr)
        if str(data.interfaceGuid) == context.guid and data.notificationSource == ww.WLAN_NOTIFICATION_SOURCE_DICT[ww.WLAN_NOTIFICATION_SOURCE_ACM]:
            if data.notificationCode == ww.WLAN_NOTIFICATION_ACM_ENUM.wlan_notification_acm_connection_start.name:
                context.start += 1
            elif context.start:
                if data.notificationCode == ww.WLAN_NOTIFICATION_ACM_ENUM.wlan_notification_acm_connection_complete.name:
                    context.end += 1
                elif data.notificationCode == ww.WLAN_NOTIFICATION_ACM_ENUM.wlan_notification_acm_connection_attempt_fail.name:
                    context.fail += 1


def wireless_connect(
        ssid,
        password,
        timeout=15,  # secs
        authentication="WPA2PSK",  # "open", 
        encryption="AES",  # "WEP",
        key_type="passPhrase",  # "networkKey", 
        interface_index=0,  # Don't modify this (until PCs with more than 1 WLAN adapter arise :) )
    ):
    interfaces = ww.getWirelessInterfaces()
    if interface_index < 0 or len(interfaces) < interface_index:
        raise WLANException(-1, "No WLAN interface for given index")
    interface = interfaces[interface_index]
    profile_name = ssid + "_profile_tmp"
    ssid_hex = "".join((hex(ord(c))[2:] for c in ssid)).upper()
    profile_string = f"""<?xml version=\"1.0\"?>
        <WLANProfile xmlns=\"http://www.microsoft.com/networking/WLAN/profile/v1\">
            <name>{profile_name}</name>
            <SSIDConfig>
                <SSID>
                    <hex>{ssid_hex}</hex>
                    <name>{ssid}</name>
                </SSID>
            </SSIDConfig>
            <connectionType>ESS</connectionType>
            <connectionMode>manual</connectionMode>
            <MSM>
                <security>
                    <authEncryption>
                        <authentication>{authentication}</authentication>
                        <encryption>{encryption}</encryption>
                        <useOneX>false</useOneX>
                    </authEncryption>
                    <sharedKey>
                        <keyType>{key_type}</keyType>
                        <protected>false</protected>
                        <keyMaterial>{password}</keyMaterial>
                    </sharedKey>
                </security>
            </MSM>
        </WLANProfile>
    """
    connection_params = {
        "connectionMode": "wlan_connection_mode_temporary_profile",
        "profile": profile_string,
        "ssid": None,
        "bssidList": None,
        "bssType": "dot11_BSS_type_infrastructure",
        "flags": WLAN_CONNECTION_HIDDEN_NETWORK,
    }

    ctx = ConnectCallbackContext(interface.guid_string, 0, 0, 0)
    notification_obj = ww.registerNotification(_wlan_connect_callback, context=ctypes.pointer(ctx))

    try:
        res = ww.connect(interface, connection_params)
    except Exception as e:
        ww.unregisterNotification(notification_obj)
        raise WLANException("WlanConnect failed") from e

    end_time = time.time() + timeout;
    while time.time() < end_time:
        time.sleep(0.5)
        if ctx.end:
            break
    ww.unregisterNotification(notification_obj)
    if ctx.end:
        if ctx.fail:
            raise WLANException(-2, "Connection failed")
    else:
        raise WLANException(-3, "Connection timed out")
    return interface.guid_string


def wireless_disconnect(interface_guid):  # Borrowed (and improved) this func from win32wifi.Win32Wifi, to avoid creting the interface when only its guid is required
    handle = ww.WlanOpenHandle()
    try:
        ww.WlanDisconnect(handle, comtypes.GUID(interface_guid))
    except Exception as e:
        raise WLANException("WlanDisconnect failed") from e
    finally:
        ww.WlanCloseHandle(handle)


def main(argv):
    if argv:
        try:
            guid = argv[0]
            print("Disconnecting wireless interface {:s} ...".format(guid))
            wireless_disconnect(guid)
        except:
            traceback.print_exc()
    else:
        try:
            print("Connecting to wireless network ...")
            ssid = "Network SSID"  # ssid and pwd here are (deliberately) dummy
            pwd = "Network password"
            guid = wireless_connect(ssid, pwd)
            print("Connected interface {:s}".format(guid))
        except:
            traceback.print_exc()


if __name__ == "__main__":
    print("Python {:s} on {:s}\n".format(sys.version, sys.platform))
    main(sys.argv[1:])
    print("\nDone.")

script.bat:

time <nul
ping www.google.com

"e:\Work\Dev\VEnvs\py_064_03.07.03_test0\Scripts\python.exe" code.py
ping www.google.com

"e:\Work\Dev\VEnvs\py_064_03.07.03_test0\Scripts\python.exe" code.py {0C58E048-BC0B-4D5F-A21F-FCD4E4B31806}
ping www.google.com

time <nul

Output:

[cfati@CFATI-5510-0:e:\Work\Dev\StackOverflow\q056721759]> sopr.bat
*** Set shorter prompt to better fit when pasted in StackOverflow (or other) pages ***

[prompt]> script.bat

[prompt]> time  0<nul
The current time is:  1:45:08.31
Enter the new time:
[prompt]> ping www.google.com
Ping request could not find host www.google.com. Please check the name and try again.

[prompt]> "e:\Work\Dev\VEnvs\py_064_03.07.03_test0\Scripts\python.exe" code.py
Python 3.7.3 (v3.7.3:ef4ec6ed12, Mar 25 2019, 22:22:05) [MSC v.1916 64 bit (AMD64)] on win32

Connecting to wireless network ...
Connected interface {0C58E048-BC0B-4D5F-A21F-FCD4E4B31806}

Done.

[prompt]> ping www.google.com

Pinging www.google.com [2a00:1450:400d:809::2004] with 32 bytes of data:
Reply from 2a00:1450:400d:809::2004: time=11ms
Reply from 2a00:1450:400d:809::2004: time=12ms
Reply from 2a00:1450:400d:809::2004: time=12ms
Reply from 2a00:1450:400d:809::2004: time=19ms

Ping statistics for 2a00:1450:400d:809::2004:
    Packets: Sent = 4, Received = 4, Lost = 0 (0% loss),
Approximate round trip times in milli-seconds:
    Minimum = 11ms, Maximum = 19ms, Average = 13ms

[prompt]> "e:\Work\Dev\VEnvs\py_064_03.07.03_test0\Scripts\python.exe" code.py {0C58E048-BC0B-4D5F-A21F-FCD4E4B31806}
Python 3.7.3 (v3.7.3:ef4ec6ed12, Mar 25 2019, 22:22:05) [MSC v.1916 64 bit (AMD64)] on win32

Disconnecting wireless interface {0C58E048-BC0B-4D5F-A21F-FCD4E4B31806} ...

Done.

[prompt]> ping www.google.com
Ping request could not find host www.google.com. Please check the name and try again.

[prompt]> time  0<nul
The current time is:  1:45:12.82
Enter the new time:

Notes:

  • In order to create a POC I had to add some code (e.g. wireless_disconnect) not necessarily related to the question, which adds complexity.
    BTW, the code is waaay more complex than I initially anticipated (that's why I didn't bother to explain it - as it would be an overkill), but I don't see any way of trimming it down
  • script.bat (and time <nul) are just to prove in console that the code is connecting / disconnecting from the wireless network (and that I'm not connecting from Win in parallel)
    • I don't know where the " 0" part from time 0<nul (in the output) comes from
  • As I specified, this is more like a POC, there are some limitations in (my and Win32Wifi) code. Some scenarios (networks) might not work without (small) code changes
  • Although connection to the network succeeds (and works), in the System Tray, the network status still appears as disconnected (actually for a fraction of a second it appears connected, but then it automatically changes). Also, the System Tray network icon shows as Connected. I'm not sure whether this is on my side (I forgot to somehow notify Win - although this doesn't make much sense), or Win doesn't like "someone else" to connect to wireless networks
  • THE MOST IMPORTANT ONE: The above code will not work OOTB, because Win32Wifi is buggy. I found 2 bugs that are fatal (critical) for this scenario, and a bunch of other smaller ones.
    I've just submitted [GitHub]: kedos/win32wifi - Fixes (some critical) and improvements. Not sure what its outcome it's going to be (considering the inactivity period).

    As an alternative, you could download the patch, and apply the changes locally. Check [SO]: Run/Debug a Django application's UnitTests from the mouse right click context menu in PyCharm Community Edition? (@CristiFati's answer) (Patching utrunner section) for how to apply patches on Win (basically, every line that starts with one "+" sign goes in, and every line that starts with one "-" sign goes out). I am using Cygwin, btw.