See remove-drivers.py
pnputil /add-driver C:\users\dolf\desktop\pre-2023-drivers\*.inf /subdirs /install /reboot| from __future__ import annotations | |
| from typing import Generator, TypedDict | |
| from subprocess import run, CalledProcessError | |
| import re | |
| import logging | |
| from datetime import date, datetime | |
| import click | |
| import yaml | |
| logger = logging.getLogger(__name__) | |
| logging.basicConfig(level=logging.DEBUG) | |
| @click.command() | |
| @click.option( | |
| "--newer-than", | |
| type=click.DateTime(formats=["%Y-%m-%d"]), | |
| help="Only prompt to remove drivers newer than this date.", | |
| required=True, | |
| ) | |
| def main(newer_than: datetime) -> None: | |
| """ | |
| Remove drivers. | |
| """ | |
| drivers = sorted( | |
| [ | |
| d | |
| for d in enum_drivers() | |
| if d.get("driver_date", date(1, 1, 1)) > newer_than.date() | |
| ], | |
| key=lambda d: d["driver_date"], | |
| reverse=True, | |
| ) | |
| for driver_info in drivers: | |
| click.echo(f"\n\nThe following driver is newer than {newer_than.date()}:") | |
| click.echo(yaml.dump(driver_info)) | |
| if click.confirm("Do you want to uninstall this driver?"): | |
| try: | |
| delete_and_uninstall_driver(driver_info) | |
| except CalledProcessError as e: | |
| logger.error(f"Failed to uninstall driver: {e}", exc_info=e) | |
| def delete_and_uninstall_driver(driver_info: DriverInfo) -> None: | |
| run( | |
| args=[ | |
| "pnputil", | |
| "/delete-driver", | |
| driver_info["published_name"], | |
| "/uninstall", | |
| "/force", | |
| ], | |
| capture_output=False, | |
| text=True, | |
| check=True, | |
| ) | |
| class DriverInfo(TypedDict, total=False): | |
| published_name: str | |
| original_name: str | |
| provider_name: str | |
| class_name: str | |
| class_guid: str | |
| class_version: str | |
| driver_version: str | |
| driver_date: date | |
| signer_name: str | |
| extension_id: str | |
| driver_version_date_regex = re.compile(r"(\d\d/\d\d/\d\d\d\d) (\d[\d\.]+)") | |
| def enum_drivers() -> Generator[DriverInfo, None, None]: | |
| completed = run( | |
| args=["pnputil", "/enum-drivers"], capture_output=True, text=True, check=True | |
| ) | |
| lines = [line.strip() for line in completed.stdout.splitlines()] | |
| info = DriverInfo() | |
| for i, line in enumerate(lines): | |
| if i == 0 and line == "Microsoft PnP Utility": | |
| continue | |
| if len(line) == 0: | |
| yield info | |
| info = DriverInfo() | |
| continue | |
| try: | |
| enum_drivers__process_line(info, line) | |
| except BaseException as e: | |
| raise ValueError(f"Error processing line {i}: {line}") from e | |
| def enum_drivers__process_line(info: DriverInfo, line: str) -> None: | |
| key, value = line.split(":", 1) | |
| key = key.strip().casefold().replace(" ", "_") | |
| value = value.strip() | |
| if key == "driver_version": | |
| # Special case: Split the driver version and date | |
| matches = driver_version_date_regex.match(value) | |
| if matches is None: | |
| logger.warning(f"Unknown driver version format: {value}") | |
| else: | |
| info["driver_date"] = datetime.strptime(matches.group(1), "%m/%d/%Y").date() | |
| info["driver_version"] = matches.group(2) | |
| else: | |
| info[key] = value # type: ignore | |
| if __name__ == "__main__": | |
| main() |