Skip to content

Instantly share code, notes, and snippets.

@rudolfbyker
Created November 2, 2023 07:26
Show Gist options
  • Save rudolfbyker/066636f6b1cae38f2a64a6d40a431d9a to your computer and use it in GitHub Desktop.
Save rudolfbyker/066636f6b1cae38f2a64a6d40a431d9a to your computer and use it in GitHub Desktop.
Remove drivers newer than a given date from Windows
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()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment