feat: NAPALM driver for Windows over PowerShell Remoting

Talks PSRP over WinRM via pypsrp, one runspace pool per connection. Each
getter sends one PowerShell script that projects cmdlet results onto flat
fields and ends in ConvertTo-Json, so the Python side parses JSON, not text.

Covers facts, interfaces, IP addresses, ARP, routes and services, plus
service start/stop/restart/enable/disable. Service names are validated and
quoted as PowerShell verbatim strings, typographic quotes included.

Fixtures are synthetic: they pin down the JSON the scripts are designed to
emit. tools/harvest.py records the real output from a host.

Refs christianmanivong/netork#300
This commit is contained in:
Christian Manivong
2026-09-24 09:23:25 +02:00
commit 1ce42ef099
18 changed files with 1435 additions and 0 deletions
+10
View File
@@ -0,0 +1,10 @@
# Synthetic fixtures
These files are what the driver's PowerShell scripts *emit*, not raw device
output: each script projects its cmdlet results onto flat, primitive fields and
ends in `ConvertTo-Json`. The shape is therefore this driver's own contract,
and the Python side is tested against it here.
What they cannot prove is that the scripts produce this shape on a real host
(property names, Windows PowerShell 5.1 serialisation quirks). They are to be
replaced by output recorded from a Windows Server and a Windows 11 client.
+8
View File
@@ -0,0 +1,8 @@
[
{"interface": "Ethernet", "ip": "10.0.0.1", "mac": "00-0D-B9-11-22-33", "state": "Reachable"},
{"interface": "Ethernet", "ip": "10.0.0.20", "mac": "00-15-5D-AA-BB-CC", "state": "Stale"},
{"interface": "Ethernet", "ip": "10.0.0.255", "mac": "FF-FF-FF-FF-FF-FF", "state": "Permanent"},
{"interface": "Ethernet", "ip": "224.0.0.22", "mac": "01-00-5E-00-00-16", "state": "Permanent"},
{"interface": "Ethernet", "ip": "10.0.0.99", "mac": "00-00-00-00-00-00", "state": "Unreachable"},
{"interface": "Ethernet", "ip": "10.0.0.98", "mac": "", "state": "Incomplete"}
]
+1
View File
@@ -0,0 +1 @@
{"hostname": "DESKTOP-4F2K9", "dns_hostname": "DESKTOP-4F2K9", "domain": "WORKGROUP", "part_of_domain": false, "manufacturer": "LENOVO", "model": "21HDCTO1WW", "serial": "PF4ABCDE", "caption": "Microsoft Windows 11 Pro", "version": "10.0.26100", "display_version": "24H2", "ubr": 1742, "uptime": 3600, "interfaces": "Wi-Fi"}
+1
View File
@@ -0,0 +1 @@
{"hostname": "SRV-APP01", "dns_hostname": "srv-app01", "domain": "corp.example", "part_of_domain": true, "manufacturer": "Microsoft Corporation", "model": "Virtual Machine", "serial": "0000-0001-2345-6789-0123-4567-89", "caption": "Microsoft Windows Server 2022 Standard", "version": "10.0.20348", "display_version": "21H2", "ubr": 2340, "uptime": 86400, "interfaces": ["Ethernet", "Ethernet 2"]}
+5
View File
@@ -0,0 +1,5 @@
[
{"name": "Ethernet", "description": "Microsoft Hyper-V Network Adapter", "status": "Up", "mtu": 1500, "speed": 10000000000, "mac": "00-15-5D-01-02-03"},
{"name": "Ethernet 2", "description": "Intel(R) Ethernet Connection I219-LM", "status": "Disconnected", "mtu": 1500, "speed": 0, "mac": "8C-16-45-AA-BB-CC"},
{"name": "Wi-Fi", "description": "Intel(R) Wi-Fi 6E AX211 160MHz", "status": "Disabled", "mtu": null, "speed": null, "mac": ""}
]
+8
View File
@@ -0,0 +1,8 @@
[
{"interface": "Ethernet", "address": "10.0.0.5", "prefix": 24, "family": "IPv4"},
{"interface": "Ethernet", "address": "10.0.0.6", "prefix": 24, "family": "IPv4"},
{"interface": "Ethernet", "address": "fe80::1c2d:3e4f:5a6b:7c8d%6", "prefix": 64, "family": "IPv6"},
{"interface": "Ethernet", "address": "2001:db8::5", "prefix": 64, "family": "IPv6"},
{"interface": "Loopback Pseudo-Interface 1", "address": "127.0.0.1", "prefix": 8, "family": "IPv4"},
{"interface": "Loopback Pseudo-Interface 1", "address": "::1", "prefix": 128, "family": "IPv6"}
]
+10
View File
@@ -0,0 +1,10 @@
[
{"destination": "0.0.0.0/0", "next_hop": "10.0.0.1", "interface": "Ethernet", "metric": 0, "protocol": "NetMgmt"},
{"destination": "10.0.0.0/24", "next_hop": "0.0.0.0", "interface": "Ethernet", "metric": 256, "protocol": "Local"},
{"destination": "192.168.50.0/24", "next_hop": "10.0.0.254", "interface": "Ethernet", "metric": 10, "protocol": "Dhcp"},
{"destination": "2001:db8::/64", "next_hop": "::", "interface": "Ethernet", "metric": 256, "protocol": "RouterAdvertisement"},
{"destination": "224.0.0.0/4", "next_hop": "0.0.0.0", "interface": "Ethernet", "metric": 256, "protocol": "Local"},
{"destination": "255.255.255.255/32", "next_hop": "0.0.0.0", "interface": "Ethernet", "metric": 256, "protocol": "Local"},
{"destination": "ff00::/8", "next_hop": "::", "interface": "Ethernet", "metric": 256, "protocol": "Local"},
{"destination": "127.0.0.0/8", "next_hop": "0.0.0.0", "interface": "Loopback Pseudo-Interface 1", "metric": 256, "protocol": "Local"}
]
+7
View File
@@ -0,0 +1,7 @@
[
{"name": "WinRM", "state": "Running", "start_mode": "Auto", "pid": 1234},
{"name": "Spooler", "state": "Stopped", "start_mode": "Manual", "pid": 0},
{"name": "MSSQL$SQLEXPRESS", "state": "Running", "start_mode": "Auto", "pid": 4321},
{"name": "RemoteRegistry", "state": "Stopped", "start_mode": "Disabled", "pid": 0},
{"name": "wuauserv", "state": "Stopped", "start_mode": "Manual", "pid": null}
]