Created
November 17, 2021 12:47
-
-
Save boppreh/091b81ab0e2f879cc6fad2ade92029de to your computer and use it in GitHub Desktop.
Parse `ipconfig /all` in Python
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import subprocess | |
import collections | |
Interface = collections.namedtuple('Interface', 'name description subnet_mask ipv4_addresses ipv4_gateway ipv6_addresses ipv6_gateway dhcp_server dns_servers') | |
def parse_ipconfig(): | |
""" | |
Parses results from ipconfig. PowerShell has more structured functions, but | |
they don't serialize properly | |
(https://stackoverflow.com/questions/69997138/serialization-differences-between-powershells-format-list-and-convertto-json). | |
A normal output looks like this: | |
Wireless LAN adapter Local Area Connection* 12: | |
Autoconfiguration Enabled . . . . : Yes | |
DHCP Enabled. . . . . . . . . . . : Yes | |
Physical Address. . . . . . . . . : 9C-DA-3E-68-11-37 | |
Description . . . . . . . . . . . : Microsoft Wi-Fi Direct Virtual Adapter | |
Connection-specific DNS Suffix . : | |
Media State . . . . . . . . . . . : Media disconnected | |
Wireless LAN adapter Local Area Connection* 1: | |
NetBIOS over Tcpip. . . . . . . . : Enabled | |
fec0:0:0:ffff::3%1 | |
fec0:0:0:ffff::2%1 | |
DNS Servers . . . . . . . . . . . : fec0:0:0:ffff::1%1 | |
DHCPv6 Client DUID. . . . . . . . : 00-01-00-01-24-A6-E0-0A-9C-DA-3E-68-11-36 | |
DHCPv6 IAID . . . . . . . . . . . : 956956711 | |
Default Gateway . . . . . . . . . : | |
Subnet Mask . . . . . . . . . . . : 255.255.255.0 | |
IPv4 Address. . . . . . . . . . . : 192.168.56.1(Preferred) | |
Link-local IPv6 Address . . . . . : fe80::d83f:9609:86ff:2b57%23(Preferred) | |
Autoconfiguration Enabled . . . . : Yes | |
DHCP Enabled. . . . . . . . . . . : No | |
Physical Address. . . . . . . . . : 0A-00-27-00-00-17 | |
Description . . . . . . . . . . . : VirtualBox Host-Only Ethernet Adapter | |
Connection-specific DNS Suffix . : | |
""" | |
result = subprocess.run(["ipconfig", "/all"], capture_output=True, text=True) | |
assert result.returncode == 0 | |
interfaces = [] | |
lines = result.stdout.strip().split('\n') | |
while lines: | |
# Parse an interface section. | |
interface_name = lines.pop(0) | |
# Ignore the empty line after the interface name. | |
assert not lines.pop(0) | |
# Parse section line by line. | |
attributes = {'Name': interface_name} | |
last_attribute_name = None | |
while lines: | |
line = lines.pop(0) | |
# Sections are separated by empty lines. | |
if not line: | |
break | |
# Fields may encode multiple values in separate, indented lines: | |
# | |
# Default Gateway . . . . . . . . . : fe80::1%8 | |
# 192.168.2.1 | |
if line.startswith(' '): | |
# We are in a continuation value, assume the name is the same | |
# as before. | |
value = line | |
attribute_name = last_attribute_name | |
else: | |
left, value = line.split(' : ') | |
attribute_name = left.strip('. ') | |
# IP addresses are often formatted as "192.168.56.1(Preferred)" | |
# or "192.168.56.1(Deprecated)". Remove those labels. | |
value = value.replace('(Preferred)', '').replace('(Deprecated)', '').strip() | |
if not value: | |
# Skip empty values. They'll be converted to None's at the end. | |
continue | |
if attribute_name == last_attribute_name: | |
# Some sections may contain duplicated fields, or fields spanning | |
# multiple lines. Put the values in a list. | |
if not isinstance(attributes[last_attribute_name], list): | |
attributes[last_attribute_name] = [attributes[last_attribute_name]] | |
attributes[last_attribute_name].append(value) | |
else: | |
if attribute_name in ('Default Gateway', 'IPv4 Address', 'IPv6 Address', 'DNS Servers'): | |
# Fields that should always be lists, even if just one value | |
# is provided. | |
value = [value] | |
attributes[attribute_name] = value | |
last_attribute_name = attribute_name | |
field_name_mapping = {'name': 'Name', 'description': 'Description', 'subnet_mask': 'Subnet Mask', 'ipv4_addresses': 'IPv4 Address', 'ipv6_addresses': 'IPv6 Address', 'dhcp_server': 'DHCP Server', 'dns_servers': 'DNS Servers'} | |
# Gateways are given with IPv4 and IPv6 mixed, and we have to fix that. | |
ipv4_gateway = '' | |
ipv6_gateway = '' | |
for gateway in attributes.get('Default Gateway', []): | |
if ':' in gateway: | |
ipv6_gateway = gateway | |
else: | |
ipv4_gateway = gateway | |
interface = Interface(ipv4_gateway=ipv4_gateway, ipv6_gateway=ipv6_gateway, **{n: attributes.get(m) for n, m in field_name_mapping.items()}) | |
interfaces.append(interface) | |
return interfaces | |
if __name__ == '__main__': | |
from pprint import pprint | |
pprint(parse_ipconfig()) | |
# Example interface: | |
# Interface(name='Ethernet adapter VirtualBox Host-Only Network:', description='VirtualBox Host-Only Ethernet Adapter', subnet_mask='255.255.255.0', ipv4_addresses=['192.168.56.1'], ipv4_gateway='', ipv6_addresses=None, ipv6_gateway='', dhcp_server=None, dns_servers=['fec0:0:0:ffff::1%1', 'fec0:0:0:ffff::2%1', 'fec0:0:0:ffff::3%1']) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment