[SOLVED] Package to configure a Wi-Fi network on Windows, Linux, and macOS

Questions about WAPT Packaging / Requests and help regarding Wapt packages.
Forum Rules
Community Forum Rules
* English support on www.reddit.com/r/wapt
* French community support is available on this forum
* Please prefix the topic title with [RESOLVED] if it is resolved.
* Please do not edit a topic that is tagged [RESOLVED]. Open a new topic referencing the old one.
* Specify the installed WAPT version, full version, and build number (2.2.1.11957 / 2.2.2.12337 / etc.) as well as the Enterprise/Discovery edition.
* Versions 1.8.2 and earlier are no longer supported. The only questions accepted regarding version 1.8.2 are related to upgrading to a supported version (2.1, 2.2, etc.).
* Specify the server OS (Linux/Windows) and version (Debian Buster/Bullseye - CentOS 7 - Windows Server 2012/2016/2019).
* Specify the OS of the administration/package creation machine and the machine with the problematic agent, if applicable (Windows 7/10/11/Debian 11/etc.).
* Avoid asking multiple questions when opening a topic, otherwise it may be ignored. If there are multiple topics, open separate topics, preferably one after the other and not all at the same time (i.e., do not spam the forum).
* Include code snippets, screenshots, and other images directly in the post. Links to Pastebin, Bitly, and other third-party sites will be systematically removed.
* As with any community forum, support is provided voluntarily by members. If you require commercial support, you can contact Tranquil IT's sales department at 02.40.97.57.55
Locked
bastien30
Messages: 38
Registration: March 8, 2024 - 3:21 PM

January 7, 2026 - 1:09 PM

Good morning,

Here is a package to configure a Wifi network on Windows (with netsh), Linux (with nmcli) and MacOS (with networksetup).

It could be greatly improved, but as it is, it works on my fleet :-)

setup.py:

Code: Select all

# -*- coding: utf-8 -*-
from setuphelpers import *
import platform

# Wifi settings
wifi_ssid = r'MY-WIFI-NETWORK'
wifi_password = r'my-wifi-password'
hidden_ssid = False

# Globally enable verbose mode
all_verbose = False

def install():
    if platform.system() == 'Windows':
        set_wifi_profile_windows(ssid=wifi_ssid, password=wifi_password, hidden=hidden_ssid, verbose=all_verbose)
    elif platform.system() == 'Darwin':
        set_wifi_profile_macos(ssid=wifi_ssid, password=wifi_password, verbose=all_verbose)
    elif platform.system() == 'Linux':
        set_wifi_profile_linux(ssid=wifi_ssid, password=wifi_password, hidden=hidden_ssid, verbose=all_verbose)
    else:
        error('Operating System not supported !')

def uninstall():
    if platform.system() == 'Windows':
        remove_wifi_profile_windows(ssid=wifi_ssid, verbose=all_verbose)
    elif platform.system() == 'Darwin':
        remove_wifi_profile_macos(ssid=wifi_ssid, verbose=all_verbose)
    elif platform.system() == 'Linux':
        remove_wifi_profile_linux(ssid=wifi_ssid, verbose=all_verbose)
    else:
        error('Operating System not supported !')


#### MACOS WIFI FUNCTIONS

def set_wifi_profile_macos(ssid, password, verbose=False):
    r"""Configure and apply WIFI profile on MacOS using networksetup

    Args:
        ssid: network SSID to connect
        password: wifi pre-shared key
        verbose: if set to True, display commands used (default: False)
        
    Notes: 
      - networksetup does not care about hidden network, this method works in all cases
      - networksetup does not care about authentication and encryption modes, it always use the most secure option possible
      - the configured SSID will be set to connect automatically by default

    For more informations see https://www.hexnode.com/mobile-device-management/help/scripts-to-manage-wi-fi-network-settings-on-macos-devices/
    """
    # Remove existings profiles first
    remove_wifi_profile_macos(ssid, verbose=verbose)
    time.sleep(2)

    print(r'Creating or updating WLAN profile %s...' % ssid)
    cmd = r'sudo networksetup -setairportnetwork en0 %s %s' % (ssid, password)
    if verbose:
        print(r'Command used: %s' % cmd)
    run(cmd)

def remove_wifi_profile_macos(ssid, verbose=False):
    r"""Remove WIFI profile if exists on MacOS using networksetup

    Args:
        ssid: profile SSID to remove
        verbose: if set to True, display command used
    """
    print(r'Removing WLAN profile %s if exists...' % ssid)
    # Note: WIFI stay connected after removal but can't reconnect after reboot or disconnection
    cmd = r'sudo networksetup -removepreferredwirelessnetwork en0 %s ' % ssid
    if verbose:
        print(r'Command used: %s' % cmd)
    run_notfatal(cmd)


#### WINDOWS FUNCTIONS

def set_wifi_profile_windows(ssid, password, authentication_mode="WPA2PSK", encryption_mode="AES", connection_mode="auto", hidden=False, verbose=False):
    r"""Configure and apply WIFI profile on Windows using netsh

    Args:
        ssid: network SSID to connect
        password: wifi pre-shared key
        authentication_mode: authentication method to use for connection (default: WPA2PSK)
        encryption_mode: encryption type to use for connection (default: AES)
        connection_mode: wether to enable wifi automatic connection, can be 'auto' or 'manual' (default: auto)
        hidden: if set to True, configure a hidden network (default: False)
        verbose: if set to True, display generated XML and commands used (default: False)
        
    For more informations see https://learn.microsoft.com/fr-fr/uwp/schemas/mobilebroadbandschema/wlan/schema-root
    """
    import tempfile

    if connection_mode not in ("auto", "manuel"):
        error("Error: connection_mode can be 'auto' or 'manual' only !")
    
    xml_template = """<?xml version="1.0"?>
<WLANProfile xmlns="http://www.microsoft.com/networking/WLAN/profile/v1">
    <name>{tmpl_ssid}</name>
    <SSIDConfig>
        <SSID>
            <name>{tmpl_ssid}</name>
        </SSID>
        <nonBroadcast>{tmpl_hidden}</nonBroadcast>
    </SSIDConfig>
    <connectionType>ESS</connectionType>
    <connectionMode>{tmpl_connection_mode}</connectionMode>
    <MSM>
        <security>
            <authEncryption>
                <authentication>{tmpl_authentication_mode}</authentication>
                <encryption>{tmpl_encryption_mode}</encryption>
                <useOneX>false</useOneX>
            </authEncryption>
            <sharedKey>
                <keyType>passPhrase</keyType>
                <protected>false</protected>
                <keyMaterial>{tmpl_password}</keyMaterial>
            </sharedKey>
        </security>
    </MSM>
</WLANProfile>"""

    if hidden:
        hidden_value = r'true'
    else:
        hidden_value = r'false'

    # Replace variables in template
    xml_data = xml_template.format(
        tmpl_ssid = ssid,
        tmpl_hidden = hidden_value,
        tmpl_connection_mode = connection_mode,
        tmpl_authentication_mode = authentication_mode,
        tmpl_encryption_mode = encryption_mode,
        tmpl_password = password
    )

    # Remove existings profiles first
    remove_wifi_profile_windows(ssid, verbose=verbose)  
    time.sleep(2)

    # Use try/finally to ensure XML file is always deleted
    try:
        # Use temporary file
        with tempfile.NamedTemporaryFile(mode="w+", suffix=".xml", delete=False, encoding="utf-8") as tmpfile:
            tmpfile.write(xml_data)
            tmpfile.flush()  # force write on disk
            xml_file = tmpfile.name

            if verbose:
                tmpfile.seek(0)
                print("XML content:\n%s" % tmpfile.read())

        print(r'Creating or updating WLAN profile %s...' % ssid)
        cmd = r'netsh wlan add profile filename=%s user=all' % xml_file
        if verbose:
            print(r'Command used: %s' % cmd)
        run(cmd)
    finally:
        remove_file(xml_file)

def remove_wifi_profile_windows(ssid, verbose=False):
    r"""Remove WIFI profile if exists on Windows using netsh

    Args:
        ssid: profile SSID to remove
        verbose: if set to True, display command used
    """
    print(r'Removing WLAN profile %s if exists...' % ssid)
    cmd = r'netsh wlan delete profile name= %s' % ssid
    if verbose:
        print(r'Command used: %s' % cmd)
    run_notfatal(cmd)


#### LINUX FUNCTIONS

def set_wifi_profile_linux(ssid, password, hidden=False, verbose=False):
    r"""Configure and apply WIFI profile on Linux using nmcli

    Args:
        ssid: network SSID to connect
        password: wifi pre-shared key
        hidden: if set to True, configure a hidden network (default: False)
        verbose: if set to True, display commands used (default: False)

        Note: 
          - nmcli does not care about authentication and encryption modes, it always use the most secure option possible
          - the configured SSID will be set to connect automatically by default

        For more informations see https://networkmanager.dev/docs/api/latest/nmcli.html
    """
    if hidden:
        hidden_value = r'yes'
    else:
        hidden_value = r'no'

    print(r'Creating or updating WLAN profile %s...' % ssid)

    # Remove existings profiles first
    remove_wifi_profile_linux(ssid, verbose=verbose)
    time.sleep(2)

    # Get interface name
    cmd = "nmcli d | grep wifi | grep -v p2p | awk \'{print $1}\'"
    if verbose:
        print(r'Getting wifi interface name with command: %s' % cmd)
    ifname = run(cmd).strip()
    if ifname == "":
        error(r'Error getting wifi interface name !')

    # Add connection
    cmd = r'nmcli con add type wifi con-name %s ssid "%s" ifname %s' % (ssid, ssid, ifname)
    if verbose:
        print(r'Creating wifi connection with command: %s' % cmd)
    run(cmd)

    # Try to connect (sometimes nmcli needs time to be able to interact with previously created connection)
    cmd = r'nmcli dev wifi con "%s" name "%s" password "%s" hidden %s' % (ssid, ssid, password, hidden_value)
    if verbose:
        print(r'Connecting to wifi network with command: %s' % cmd)
    try_nb = 12
    sleep_duration = 5
    for i in range(1, try_nb):
        time.sleep(sleep_duration)
        try:
            print(r'Trying to connect... %s/%s ' % (i, try_nb), end="")
            run(cmd)
        except:
            print(r'FAILED')
            continue
        else:
            print(r'SUCCESS')
            return True
    error(r'Error: cannot connect to wifi network %s after %s attempts !' % (ssid, try_nb))

def remove_wifi_profile_linux(ssid, verbose=False):
    r"""Remove WIFI profile if exists on Linux using nmcli

    Args:
        ssid: profile SSID to remove
        verbose: if set to True, display command used
    """
    print(r'Removing WLAN profile %s if exists...' % ssid)
    cmds = [r'nmcli con delete "%s" ' % ssid,
            r'nmcli con delete "Auto %s" ' % ssid] # Linux Mint create a "Auto <SSID>" connection when configured through GUI, ensure it's deleted too
    if verbose:
        print(r'Commands used: %s' % cmds)
    for cmd in cmds:
        run_notfatal(cmd)
User avatar
vcardon
WAPT Expert
Messages: 278
Registration: Oct 06, 2017 - 10:55 p.m.
Location: Nantes, France

January 8, 2026 - 7:09 PM

Hello bastien30

, thank you for your package. I suggest an improvement based on https://www.wapt.fr/fr/doc/wapt-create- ... se-feature.

Indeed, passwords in WAPT packages are sensitive and confidential data, and WAPT packages are publicly visible by design.

The tip suggested in the link is an excellent method for protecting sensitive information contained in a WAPT package.
Vincent CARDON
Tranquil IT
bastien30
Messages: 38
Registration: March 8, 2024 - 3:21 PM

January 8, 2026 - 10:16 PM

Hello Vincent,

To be frank, my package does integrate this mechanism, but I'm basing it on an older version of this package that I've modified a bit.
Although functional, it is certainly less optimized than the latest version available on the store.
I use it in other packages I made some time ago... so it's faster to copy and paste when I need it :roll:

Here's the full version of the coup!

setup.py:

Code: Select all

# -*- coding: utf-8 -*-
from setuphelpers import *
import base64
import platform

wifi_ssid = r'MY-WIFI-NETWORK'
hidden_ssid = False
encrypted_txt_file = makepath(r'certs', r'encrypt-txt.json')

all_verbose = False

def install():
    # Decrypt WIFI password if hosts_uuid exists in encrypted txt file
    encryptlist = json_load_file(encrypted_txt_file)
    if WAPT.host_uuid in encryptlist:
        host_key = WAPT.get_host_key()
        encrypted_txt = host_key.decrypt(base64.b64decode(encryptlist[WAPT.host_uuid])).decode('utf-8')
    else:
        print('Host UUID %s not found in %s, nothing to do !' % (WAPT.host_uuid, encrypted_txt_file))
        return 0

    if platform.system() == 'Windows':
        set_wifi_profile_windows(ssid=wifi_ssid, password=encrypted_txt, hidden=hidden_ssid, verbose=all_verbose)
    elif platform.system() == 'Darwin':
        set_wifi_profile_macos(ssid=wifi_ssid, password=encrypted_txt, verbose=all_verbose)
    elif platform.system() == 'Linux':
        set_wifi_profile_linux(ssid=wifi_ssid, password=encrypted_txt, hidden=hidden_ssid, verbose=all_verbose)
    else:
        error('Operating System not supported !')

def uninstall():
    if platform.system() == 'Windows':
        remove_wifi_profile_windows(ssid=wifi_ssid, verbose=all_verbose)
    elif platform.system() == 'Darwin':
        remove_wifi_profile_macos(ssid=wifi_ssid, verbose=all_verbose)
    elif platform.system() == 'Linux':
        remove_wifi_profile_linux(ssid=wifi_ssid, verbose=all_verbose)
    else:
        error('Operating System not supported !')


#### MACOS WIFI FUNCTIONS

def set_wifi_profile_macos(ssid, password, verbose=False):
    r"""Configure and apply WIFI profile on MacOS using networksetup

    Args:
        ssid: network SSID to connect
        password: wifi pre-shared key
        verbose: if set to True, display generated XML and command used (default: False)
        
    Notes: 
      - networksetup does not care about hidden network, this method works in all cases
      - networksetup does not care about authentication and encryption modes, it always use the most secure option possible
      - the configured SSID will be set to connect automatically by default

    For more informations see https://www.hexnode.com/mobile-device-management/help/scripts-to-manage-wi-fi-network-settings-on-macos-devices/
    """
    # Remove existings profiles first
    remove_wifi_profile_macos(ssid, verbose=verbose)
    time.sleep(2)

    print(r'Creating or updating WLAN profile %s...' % ssid)
    cmd = r'sudo networksetup -setairportnetwork en0 %s %s' % (ssid, password)
    if verbose:
        print(r'Command used: %s' % cmd)
    run(cmd)

def remove_wifi_profile_macos(ssid, verbose=False):
    r"""Remove WIFI profile if exists on MacOS using networksetup

    Args:
        ssid: profile SSID to remove
        verbose: if set to True, display command used
    """
    print(r'Removing WLAN profile %s if exists...' % ssid)
    # Note: WIFI stay connected after removal but can't reconnect after reboot or disconnection
    cmd = r'sudo networksetup -removepreferredwirelessnetwork en0 %s ' % ssid
    if verbose:
        print(r'Command used: %s' % cmd)
    run_notfatal(cmd)


#### WINDOWS FUNCTIONS

def set_wifi_profile_windows(ssid, password, authentication_mode="WPA2PSK", encryption_mode="AES", connection_mode="auto", hidden=False, verbose=False):
    r"""Configure and apply WIFI profile on Windows using netsh

    Args:
        ssid: network SSID to connect
        password: wifi pre-shared key
        authentication_mode: authentication method to use for connection (default: WPA2PSK)
        encryption_mode: encryption type to use for connection (default: AES)
        connection_mode: wether to enable wifi automatic connection, can be 'auto' or 'manual' (default: auto)
        hidden: if set to True, configure a hidden network (default: False)
        verbose: if set to True, display generated XML and command used (default: False)
        
    For more informations see https://learn.microsoft.com/fr-fr/uwp/schemas/mobilebroadbandschema/wlan/schema-root
    """
    import tempfile

    if connection_mode not in ("auto", "manuel"):
        error("Error: connection_mode can be 'auto' or 'manual' only !")
    
    xml_template = """<?xml version="1.0"?>
<WLANProfile xmlns="http://www.microsoft.com/networking/WLAN/profile/v1">
    <name>{tmpl_ssid}</name>
    <SSIDConfig>
        <SSID>
            <name>{tmpl_ssid}</name>
        </SSID>
        <nonBroadcast>{tmpl_hidden}</nonBroadcast>
    </SSIDConfig>
    <connectionType>ESS</connectionType>
    <connectionMode>{tmpl_connection_mode}</connectionMode>
    <MSM>
        <security>
            <authEncryption>
                <authentication>{tmpl_authentication_mode}</authentication>
                <encryption>{tmpl_encryption_mode}</encryption>
                <useOneX>false</useOneX>
            </authEncryption>
            <sharedKey>
                <keyType>passPhrase</keyType>
                <protected>false</protected>
                <keyMaterial>{tmpl_password}</keyMaterial>
            </sharedKey>
        </security>
    </MSM>
</WLANProfile>"""

    if hidden:
        hidden_value = r'true'
    else:
        hidden_value = r'false'

    # Replace variables in template
    xml_data = xml_template.format(
        tmpl_ssid = ssid,
        tmpl_hidden = hidden_value,
        tmpl_connection_mode = connection_mode,
        tmpl_authentication_mode = authentication_mode,
        tmpl_encryption_mode = encryption_mode,
        tmpl_password = password
    )

    # Remove existings profiles first
    remove_wifi_profile_windows(ssid, verbose=verbose)  
    time.sleep(2)

    # Use try/finally to ensure XML file is always deleted
    try:
        # Use temporary file
        with tempfile.NamedTemporaryFile(mode="w+", suffix=".xml", delete=False, encoding="utf-8") as tmpfile:
            tmpfile.write(xml_data)
            tmpfile.flush()  # force write on disk
            xml_file = tmpfile.name

            if verbose:
                tmpfile.seek(0)
                print("XML content:\n%s" % tmpfile.read())

        print(r'Creating or updating WLAN profile %s...' % ssid)
        cmd = r'netsh wlan add profile filename=%s user=all' % xml_file
        if verbose:
            print(r'Command used: %s' % cmd)
        run(cmd)
    finally:
        remove_file(xml_file)

def remove_wifi_profile_windows(ssid, verbose=False):
    r"""Remove WIFI profile if exists on Windows using netsh

    Args:
        ssid: profile SSID to remove
        verbose: if set to True, display command used
    """
    print(r'Removing WLAN profile %s if exists...' % ssid)
    cmd = r'netsh wlan delete profile name= %s' % ssid
    if verbose:
        print(r'Command used: %s' % cmd)
    run_notfatal(cmd)


#### LINUX FUNCTIONS

def set_wifi_profile_linux(ssid, password, hidden=False, verbose=False):
    r"""Configure and apply WIFI profile on Linux using nmcli

    Args:
        ssid: network SSID to connect
        password: wifi pre-shared key
        hidden: if set to True, configure a hidden network (default: False)
        verbose: if set to True, display generated XML and command used (default: False)

        Note: 
          - nmcli does not care about authentication and encryption modes, it always use the most secure option possible
          - the configured SSID will be set to connect automatically by default

        For more informations see https://networkmanager.dev/docs/api/latest/nmcli.html
    """
    if hidden:
        hidden_value = r'yes'
    else:
        hidden_value = r'no'

    print(r'Creating or updating WLAN profile %s...' % ssid)

    # Remove existings profiles first
    remove_wifi_profile_linux(ssid, verbose=verbose)
    time.sleep(2)

    # Get interface name
    cmd = "nmcli d | grep wifi | grep -v p2p | awk \'{print $1}\'"
    if verbose:
        print(r'Getting wifi interface name with command: %s' % cmd)
    ifname = run(cmd).strip()
    if ifname == "":
        error(r'Error getting wifi interface name !')

    # Add connection
    cmd = r'nmcli con add type wifi con-name %s ssid "%s" ifname %s' % (ssid, ssid, ifname)
    if verbose:
        print(r'Creating wifi connection with command: %s' % cmd)
    run(cmd)

    # Try to connect (sometimes nmcli needs time to be able to interact with previously created connection)
    cmd = r'nmcli dev wifi con "%s" name "%s" password "%s" hidden %s' % (ssid, ssid, password, hidden_value)
    if verbose:
        print(r'Connecting to wifi network with command: %s' % cmd)
    try_nb = 12
    sleep_duration = 5
    for i in range(1, try_nb):
        time.sleep(sleep_duration)
        try:
            print(r'Trying to connect... %s/%s ' % (i, try_nb), end="")
            run(cmd)
        except:
            print(r'FAILED')
            continue
        else:
            print(r'SUCCESS')
            return True
    error(r'Error: cannot connect to wifi network %s after %s attempts !' % (ssid, try_nb))

def remove_wifi_profile_linux(ssid, verbose=False):
    r"""Remove WIFI profile if exists on Linux using nmcli

    Args:
        ssid: profile SSID to remove
        verbose: if set to True, display command used
    """
    print(r'Removing WLAN profile %s if exists...' % ssid)
    cmds = [r'nmcli con delete "%s" ' % ssid,
            r'nmcli con delete "Auto %s" ' % ssid] # Linux Mint create a "Auto <SSID>" connection when configured through GUI, ensure it's deleted too
    if verbose:
        print(r'Commands used: %s' % cmds)
    for cmd in cmds:
        run_notfatal(cmd)
update_package.py:

Code: Select all

# -*- coding: UTF-8 -*-
from setuphelpers import *
import json
import base64
from waptcrypto import SSLCertificate
import waptguihelper

encrypted_txt_file = makepath(r'certs', r'encrypt-txt.json')

def update_package():
    # Get server URL
    urlserver = inifile_readstring(makepath(install_location('WAPT_is1'),'wapt-get.ini'),'global','wapt_server').replace('https://','')

    # Connect to API
    print(r'Asking user for WAPT server credentials...')
    credentials_url = waptguihelper.login_password_dialog('Credentials for wapt server',urlserver,'admin','')
    WAPT.waptserver.post('api/v3/login' ,data=json.dumps({'user': credentials_url["user"], 'password':credentials_url["password"]}))

    # Get WIFI password
    print(r'Asking user for WIFI password...')
    encrypted_txt = waptguihelper.input_dialog("Encrypting with WAPT", "Enter the WIFI password: ", "")
    if not encrypted_txt:
        error("Please provide WIFI password !")

    # Remove certs directory and recreate it empty
    if isdir("certs"):
        remove_tree("certs")
    mkdirs("certs")

    # Get certificates from all hosts
    data = WAPT.waptserver.get("api/v3/hosts?columns=host_certificate&limit=1000000")["result"]

    # Encrypt WIFI password with all certificates
    print(r'Encrypting WIFI password with all hosts certificates...')
    encrypt_dict = {}
    for value in data:
        if value["host_certificate"]:
            host_cert = SSLCertificate(crt_string=value["host_certificate"])
            encrypt_dict[value["uuid"]] = base64.b64encode(host_cert.encrypt(encrypted_txt.encode("utf-8"))).decode("utf-8")
            print(value["computer_fqdn"] + ":" + value["uuid"] + ":" + encrypt_dict[value["uuid"]])

    # Write encrypted password to json file
    json_write_file(encrypted_txt_file, encrypt_dict)

    print("\nPackage updated with success !")
And the small text file that I add to these kinds of packages, so that my colleagues know roughly how it works, and how to update it:

Code: Select all

## Principe :

- on chiffre le mot de passe wifi avec chacune des clés publique de chacun des agents WAPT et on l'exporte dans un fichier texte
- lors de l'installation, chacun des agents WAPT pourra déchiffrer le mot de passe grace à sa clé privée

-> ainsi, le mot de passe wifi n'est pas en clair dans le paquet (il l'est uniquement le temps de l'installation)


## Mettre à jour le paquet :

- dans VSCode, lancer l'update-package depuis le menu de gauche

- entrer les identifiants d'accès à WAPT (identique au mot de passe de la console)

- lorsque c'est demandé, entrer le mot de passe wifi

- si aucune erreur, la console en bas de VSCode doit afficher "Package updated with success !"

- on supprime le dossier __pycache__ à la racine du paquet

- on incrémente le numéro de version du paquet (la partie après le tiret)

- on peut build-upload le paquet ;)
User avatar
dcardon
WAPT Expert
Messages: 1929
Registration: June 18, 2014 - 09:58
Location: Saint Sébastien sur Loire
Contact :

January 12, 2026 - 10:07

Hi Bastien,

that's a pretty good package. :-) I think it could be inspiring for many other WAPT admins to show them the power of the tool.

Regarding Wi-Fi authentication, if you can configure your APs in EAP-TLS RADIUS authentication mode, Simon has developed an ADCS (Active Directory Certificate Services) equivalent [1] for distributing certificates to domain-joined client workstations. The main objective of this new tool is to facilitate the distribution of certificates to user workstations for 802.1x authentication (now that Microsoft has removed MS-CHAPv2 authentication in Windows 11) or VPN.

If you have some time, I encourage you to test the tool. If it allows you to eliminate a few passwords here and there, that's a good thing. :-)

See you soon and Happy New Year 2026!

Denis

[1] https://github.com/tranquilit/adcs_python
Denis Cardon - Tranquil IT
Share your experiences on WAPT! Send us your blog and article URLs in the "Your Opinion of the forum, and we'll feature them on the WAPT
Locked