На ПК одного из коллег возникла острая необходимость высвободить как можно больше пространства на диске. Стандартные средства очистки и ручное удаление ненужного лишь незначительно улучшили ситуацию.
Я заинтересовался вопросом и применил свое руководство на практике. Помимо прочего мое внимание привлекло хранилище драйверов, занимающее 4GB. Сегодня я покажу, как автоматизировать анализ и очистку хранилища.
[+] Сегодня в программе
- Общие сведения
- Как образуются дубликаты драйверов
- Нужно ли удалять дубликаты драйверов
- Как определяются дубликаты драйверов
- Безопасно ли удалять дубликаты драйверов
- Зачем нужен скрипт, если есть Driver Store Explorer
- Скрипт PowerShell
- Параметры скрипта
- Получение списка дубликатов
- Подсчет размера дубликатов
- Бэкап и удаление дубликатов
- Рекомендации по использованию скрипта
- Дискуссия
Общие сведения
Сначала немного теории в формате «вопрос – ответ».
Как образуются дубликаты драйверов
Хранилище драйверов находится в папке C:\Windows\System32\DriverStore. Когда система определяет устройство, она пытается установить для него подходящий драйвер из хранилища. Если там драйвера нет, ОС ищет подходящий в Windows Update и устанавливает его с серверов Microsoft. Вы также можете установить драйвер вручную, например, скачав его с сайта изготовителя. В любом случае установленный драйвер помещается в хранилище.
По мере работы ОС могут выходить обновления драйверов. Вне зависимости от способа установки новые версии также попадают в хранилище. Наличие в нем предыдущей версии драйвера обеспечивает его откат, который вы можете выполнить в диспетчере устройств в случае возникновения проблемы с новым драйвером.
Нужно ли удалять дубликаты драйверов
В подавляющем большинстве случаев удаление вручную или скриптом не требуется, потому что:
- Хранилище драйверов редко достигает существенного размера относительно общего объема диска.
- В Windows 8.1 и 10 предусмотрена автоматическая очистка старых пакетов драйверов. Windows 10 с контролем памяти «на автомате» в целом хорошо с этим справляется без ручного вмешательства (в классической утилите cleanmgr тоже имеется опция очистки).
Но есть сценарии, при которых очистка дубликатов может иметь смысл, например:
- У вас маленький сами знаете что, и в этой ситуации хранилище драйверов занимает значимый процент диска.
- В некоторых случаях системная очистка игнорирует различные версии одного драйвера. Мне не удалось найти документацию по ее алгоритму, поэтому о причинах можно только гадать.
Сочетание этих факторов вкупе с реальной необходимостью освободить как можно больше места может быть поводом к анализу хранилища драйверов и удалению дубликатов. На системе моего коллеги именно так и было — 2.7GB дубликатов драйверов в хранилище занимали 2% SSD объемом 120GB.
10.0.17134 Total size of duplicate drivers: 2759.8 MB ClassDescription Provider Driver Size (MB)Name Version Date BootCritical ---------------- ------------ ------ ---- ---- ------- ---- ------------ Extensions Intel oem17.inf 0 hdbusext.inf 26.20.100.7263 2019-09-25 False Extensions Intel oem22.inf 0 hdbusext.inf 27.20.100.7989 2020-06-05 False System devices Intel oem15.inf 0,3 heci.inf 1815.12.0.2021 2018-04-11 True Display adapters Intel oem7.inf 814,7 igdlh64.inf 23.20.16.4944 2018-02-04 False Display adapters Intel oem16.inf 1004,1 igdlh64.inf 26.20.100.7263 2019-09-25 False Display adapters Intel oem21.inf 907,3 igdlh64.inf 27.20.100.7989 2020-06-05 False Sound, video and game controllers Intel(R) oem23.inf 5,7 intcdaud.inf 10.26.0.9 2019-12-03 False Sound, video and game controllers Intel(R) oem18.inf 5,7 intcdaud.inf 10.26.0.8 2019-05-17 False Sound, video and game controllers Intel(R) oem19.inf 7,1 intcdaud.inf 10.27.0.5 2019-08-22 False Sound, video and game controllers Intel(R) oem6.inf 7,6 intcdaud.inf 10.24.0.3 2017-12-06 False Sound, video and game controllers Intel(R) oem24.inf 7,1 intcdaud.inf 10.27.0.8 2020-01-21 False Network adapters Cisco oem11.inf 0,1 vpnva-6.inf 3.1.6019.0 2014-02-26 False Network adapters Cisco oem10.inf 0,1 vpnva-6.inf 3.1.4065.0 2013-08-30 False
Это, кстати, вывод скрипта, который я разберу ниже.
Как определяются дубликаты драйверов
Про встроенную очистку мне неведомо, но в общем случае подход несложный. У драйвера есть исходное имя файла вида igdlh64.inf
, а каждая версия хранится в отдельной папке с невзрачным именем вида oem16.inf
наряду с сопутствующими файлами.
Соответственно, если в хранилище на некое исходное имя драйвера приходится более одной версии, получается как минимум один дубликат. В теории есть небольшая вероятность случайного совпадения исходных имен для разных драйверов, но в пределах одного класса устройств и поставщика это практически исключено.
Безопасно ли удалять дубликаты драйверов
Удаление любым поддерживаемым способом — относительно безопасная операция, даже если вы ошибочно указали не ту версию драйвера. Windows не даст снести использующийся в настоящий момент драйвер. Для этого надо сначала удалить устройство, что несложно, однако выходит за рамки статьи.
Однако после удаления всех дубликатов конкретного драйвера невозможно будет откатить драйвер к предыдущей версии. Это должно быть очевидно, но на всякий случай предупреждаю.
Зачем нужен скрипт, если есть Driver Store Explorer
В домашних условиях вы можете задействовать удобную утилиту Driver Store Explorer с графическим интерфейсом и открытым исходным кодом.
Однако для удаленной диагностики скрипт может оказаться проще, чем инструктирование пользователя, особенно неопытного. В ограниченной корпоративной среде применение сторонней утилиты может быть в принципе невозможно. Скрипт задействует только встроенные системные средства и выводит более емкую картину.
Скрипт PowerShell
Вообще, для меня эта запись не столько про удаление драйверов, сколько про PowerShell :) Потому что столкнувшись с задачей по автоматизации, я всегда стараюсь развить навыки, ибо они пригодятся и в будущем.
Сбор и вывод сведений должен работать в Windows 8.1+, а резервное копирование и удаление – только в Windows 10 1607+ (подробнее о причинах ниже).
👉 Скачать скрипт dupe-drivers.ps1 в архиве (инструкции по разблокировке и запуску).
В принципе, задача удаления дубликатов решается парой команд, но строк в скрипте больше. С одной стороны, так удобнее читать, а с другой – хотелось дополнительной информации для анализа. Дальше я разберу ключевые составляющие скрипта.
Параметры скрипта
Вы можете запускать скрипт с параметрами:
-Outfile
— путь к файлу с результатами. По умолчанию — рабочий стол.-Backup
— путь к папке для бэкапа драйверов. По умолчанию –C:\drivers-backup-YYYY-MM-DD
, но лучше указать другой диск, конечно.-HideRollback
— исключение из анализа/вывода драйверов, использующихся для отката текущего драйвера. По умолчанию исключаются только версии, используемые системой, т.е. самые новые. Параметр не принимает значения.
Получение списка дубликатов
Красота PowerShell в том, что непростая на первый взгляд цель достигается одной командой!
$dupe = @() Get-WindowsDriver -Online | Select-Object ClassDescription, ProviderName, Driver, Version, Date, BootCritical, @{name="Name"; expression= {Split-Path -Path $_.OriginalFileName -Leaf}}, @{name="Cat"; expression= {Split-Path -Path $_.OriginalFileName}} | Group-Object -Property Name | Where-Object {$_.Count -gt 1} | ForEach-Object {$dupe += $_.Group | Sort-Object Date -Descending | Select-Object -Skip 1}
В первой строке просто объявляется массив для сбора в него дубликатов. Затем в дело вступает мощный конвейер. Это фактически «ванлайнер», но я разбил код на строки для наглядности:
- Get-WindowsDriver получает список всех сторонних драйверов. Для вывода абсолютно всех драйверов можно использовать параметр
-All
, но здесь это не нужно. - Select-Object формирует список нужных для анализа свойств драйвера. Прелесть в том, что можно на лету преобразовывать их значения. Так в свойстве
OriginalFileName
содержится полный путь к исходному INF-файлу. У меня в строках 4 и 5: - Имя
OriginalFileName
заменяется наName
для краткости, а значение сокращается до имени файла с помощью командлета Split-Path с параметром-Leaf
(последний элемент пути). - Имя
Cat
присваивается пути к родительской папке драйвера с помощью того же Split-Path, но уже без-Leaf
. Путь пригодится дальше для подсчета размера дубликатов. - Group-Object группирует драйверы по исходному имени, а Where-Object отбирает имена, для которых имеется более одной группы. Логика та же, что в разделе статьи про определение дубликатов. На этом этапе такая картина:
Count Name Group ----- ---- ----- 4 tplcd.inf {@{ClassDescription=Monitors; ProviderName=Lenovo; Driver=oem16.inf; Version=6.13.3.... 2 powermgr.inf {@{ClassDescription=System devices; ProviderName=Lenovo; Driver=oem33.inf; Version=1... 2 nvhda.inf {@{ClassDescription=Sound, video and game controllers; ProviderName=NVIDIA Corporati... 2 itpcdless.inf {@{ClassDescription=Human Interface Devices; ProviderName=Microsoft; Driver=oem49.in... 2 ipcdless.inf {@{ClassDescription=Human Interface Devices; ProviderName=Microsoft; Driver=oem56.in...
- Наконец, с помощью ForEach-Object для каждой группы выполняется вложенный конвейер:
- Sort-Object сортирует драйверы по дате (новые сверху)
- Select-Object с параметром
-Skip 1
выбирает все объекты кроме первого, т.е. кроме самого нового драйвера $dupe +=
добавляет эти объекты в ранее объявленный массив
В итоге массив $dupe
уже содержит все дубликаты драйверов, т.е. только предыдущие версии, дальнейшая работа ведется уже с ним. Теперь можно удалить все дубликаты одной командой, но хочется проанализировать ситуацию.
Подсчет размера дубликатов
В NTFS нет понятия размер папки, поэтому файловые менеджеры подсчитывают его динамически, исходя из общего размера файлов в папке. В PowerShell это тоже достигается перебором всех файлов в папке и суммированием их свойства Length
(размера в байтах), хотя отдельный командлет не помешал бы. Это не самый эффективный способ, есть и побыстрее — модуль GetSTFolderSize, но для небольшого количества папок и файлов его достаточно.
$dupe | ForEach-Object { $totalsize += [math]::Round(((Get-ChildItem $_.Cat -Recurse | Measure-Object length -Sum).Sum)/1mb,1) }
Для каждого дубликата подсчитывается размер файлов в родительской папке драйвера Cat
, округляется до одного знака после запятой и добавляется в переменную $totalsize
. В итоге получается общий размер дубликатов.
Поскольку по ходу дела вычисляется размер каждого дубликата, хочется заодно добавить его в сводку, т.е. в объект $dupe
. Для этого предусмотрен командлет Add-Member, с которым я раньше не пересекался в практических задачах.
Здесь у меня случился единственный затык – не работал конвейер вида $dupe | Add-Member
. Василий Гусев объяснил, что этот командлет не выполняет скриптблок (здесь – подсчет размера файлов), а сохраняет его в значение.
Поэтому в рамках перебора дубликатов пришлось помещать размер каждой папки в переменную, а затем уже добавлять ее значение в объект. $totalsize
все так же подсчитывает общий размер.
$dupe | ForEach-Object { $s = [math]::Round(((Get-ChildItem $_.Cat -Recurse | Measure-Object length -Sum).Sum)/1mb,1) #Add size info to the dupe driver object $_ | Add-Member -MemberType NoteProperty -Name Size -Value $s #Calculate total size of dupe drivers $totalsize += $s }
Однако в комментариях Андрей предложил более оптимальный вариант — добавлять свойство Size
на этапе формирования объекта, что делает Add-Member ненужным на этом этапе. Скрипт обновлен, но в статье я оставил как было.
Вывод результатов на экран и сохранение в файл я разбирать в деталях не буду. Однако хочу обратить ваше внимание на командлет Tee-Object. С его помощью можно в один прием записать результат в файл и вывести в консоль.
Бэкап и удаление дубликатов
Прежде чем удалять драйверы, имеет смысл сделать их резервную копию. В скрипте команды бэкапа и удаления драйверов закомментированы.
Бэкап
Начиная с Windows 8.1 Update 1 для экспорта всех сторонних драйверов можно задействовать dism.exe с ключом /Export-Driver или командлет PowerShell Export-WindowsDriver. Утилита pnputil обрела такую возможность в Windows 10 1607, причем с выбором драйверов для экспорта.
Бэкап всех дубликатов. Массив просто передается по конвейеру утилите pnputil.
$dupe | ForEach-Object {pnputil /export-driver $_.Driver $Backup}
Бэкап только новейших дубликатов. Мне больше нравится вариант резервного копирования только того драйвера, который [предположительно] предлагается для отката в диспетчере устройств. Учтите, что при запуске скрипта с параметром -HideRollback
откатная версия не удаляется, а в резервную копию попадает та, что была установлена перед ней.
$dupe | Group-Object -Property Name | ForEach-Object { $_.Group | Select-Object -First 1 | ForEach-Object { pnputil /export-driver $_.Driver $Backup } }
Здесь я снова группирую драйверы по имени и выбираю нужные командлетом Select-Object. Выше я с помощью -Skip 1
отбрасывал новейший драйвер, т.е. используемый системой, чтобы оставить только дубликаты. А сейчас выбираю среди дубликатов самый новый, т.е. первый, с помощью -First 1
.
Удаление
Парадоксально, но в инструментарии DISM не предусмотрено удаление драйвера из запущенной системы. Командлет PowerShell Remove-WindowsDriver и ключ dism.exe /Remove-Driver работают только с автономными образами. Зато такая функция есть в утилите pnputil, которая активно развивается в Windows 10, что следует из документации.
Отмечу, что pnputil постепенно вобрала в себя ряд функций консольного диспетчера устройств devcon. Теперь утилиты и модуля PowerShell PnpDevice уже достаточно для автоматизации различных типовых задач вроде удаления или перезапуска устройств.
Удаление дубликатов конкретного драйвера. В данном случае выборка ведется по исходному имени. Как правило, дубликаты драйвера видео занимают больше всего места, а остальные можно вообще не трогать.
$dupe | Where-Object {$_.Name -eq 'igdlh64.inf'} | ForEach-Object {pnputil /delete-driver $_.Driver}
Аналогичной выборкой вы можете удалить драйверы определенной версии или целого класса, однако учтите, что название класса варьируется в зависимости от локализации.
Удаление всех дубликатов. Весь массив передается по конвейеру утилите pnputil.
$dupe | ForEach-Object {pnputil /delete-driver $_.Driver}
Рекомендации по использованию скрипта
Публикация статьи и скрипта не является призывом к удалению дубликатов драйверов. Я делюсь с вами способом и инструментом, который вы можете подстроить под свою ситуацию по результатам анализа.
- Не выполняйте удаление вслепую, сначала изучите расклад. Чистка не имеет смысла без существенной экономии.
- Не удаляйте драйверы без предварительного бэкапа. Его можно зачистить спустя какое-то время, если места нет.
- Оставляйте в хранилище последний дубликат, сохраняя возможность отката (вариант для самых осторожных). Это достигается запуском скрипта с параметром
-HideRollback
.
Дискуссия
Давайте посмотрим, как у вас обстоят дела с дубликатами драйверов! В комментариях опубликуйте:
- Ссылку на pastebin с результатами выполнения скрипта.
- Ожидаемый размер экономии от удаления пакетов драйверов в утиите cleanmgr, запущенной из командной строки от имени администратора.
- Объем системного раздела из свойств диска. Это позволяет оценить долю пространства, занятого старыми версиями.
- Дату первоначальной установки ОС. Это может дать дополнительную пищу для размышлений. Тему я разбирал совсем недавно.
Vladislav Jandjuk
Маленький нюанс — Десктоп пользователя не обязательно там, где у Вас. Кроме того, бывают локализованные системы.
Правильнее как-то так:
А так у меня 0 байт :)
Vadim Sterkin
Согласен, уже не первый раз забываю про перенос. По кр. мере я сделал путь параметром :) Поправил скрипт, спасибо!
Vadim Sterkin
Добавил в скрипт параметр
-HideRollback
. С ним не будут учитываться / выводиться не только последние версии драйверов (используемые системой), но и предпоследние (для отката драйвера).Все-таки скрипт изначально рассчитан на максимальную очистку, что сносит откатные версии, о чем я предупреждаю в статье.
ALF Zetas
раз в полгода, при очередном апгрейде винды, дубликаты драйверов сами удаляются — это нужно много лет не обновлять винду, чтоб насобирать гиг дубликатов ;)
Vadim Sterkin
В этом что-то есть, но есть и нюансы.
Если человек экспериментирует с драйверами видео в целях поиска оптимального, то сложить в хранилище несколько лишних гигабайт можно легко.
Сейчас апгрейд (переустановка поверх) уже не раз в полгода, а раз в год. При этом принудительный — раз в 13-14 месяцев.
В корпоративных осенних изданиях поддержка 30 месяцев, а в примере из статьи (1803) — продлена до трех лет http://aka.ms/win10releaseinfo
Так что применение скрипту может найтись. Да и родился он из практической задачи. Но даже если применения конкретно этому скрипту нет, то приемы PowerShell вполне тиражируемые.
Vadim Sterkin
У меня есть сомнения в абсолютной точности данного утверждения. Из моих ПК однозначных выводов сделать не могу, но вот пример https://pastebin.com/U8diNaTG одного из участников чата с 20H2, т.е. переустановка поверх была при обновлении до 20H1.
Я скопирую сюда, т.к. гостевой PasteBin не вечен.
Видно, что для intcdaud.inf все три версии вышли до обновления до 20H1 (весна/лето 2020), но две самых старых не удалены.
Алексей Каманин
Всё никак руки не дойдут сделать то же самое для установочных пакетов, хранимых виндой.
Vadim Sterkin
В смысле Windows\Installer? Кмк это посложнее будет, но главный источник засорения папки — обновления Office MSI. Переход на Click-to-Run решает вопрос, после чего отпадает необходимость ковыряться в Installer.
Впрочем, скрипт есть https://gallery.technet.microsoft.com/Delete-unused-msi-and-msp-aecf0bc8 (ну или пока есть, ибо галерея текнет закрывается в конце 2020).
Андрей
Оптимизация.
Так как из Get-WindowsDriver вы делаете кастомный объект, то в конце добавьте свойство Size
чтобы потом не использовать add-member.
А этот цикл сделать лаконичнее:
Итоговую таблицу я бы отсортировал так:
чтобы версии и названия, а не имена файлов шли по порядку.
Vadim Sterkin
Про Size я не подумал, отличная идея, спасибо.
Насчет сортировки — не уверен. В пределах одного класса версии драйверов для разных устройств могут перемешаться. По именам однозначно видно все дубликаты одного драйвера. Можно делать так
Но вообще, меньшая версия может быть новее. Вот пример
Андрей
Еще немного оптимизации. Входной параметр лучше так:
Vadim Sterkin
У меня так и было изначально, но см. первый комментарий :)
Андрей
это не зависит от языка. desktop он и в «африке» (в системе) desktop
Vadim Sterkin
Конечно, но он может быть перенесен из профиля.
Ilya K
Интересно было бы увязать драйвер с конкретным устройством. У драйвера есть HardwareId и CompatibleId (они специфичны для шины, но обычно по ним можно понять, о чем речь), но по какой-то причине на моей Windows они пустые.
А у Get-pnpDevice нет драйвера:-/
Вероятно, информация есть в WMI, и конечно она есть в .inf файле, но придется много писать.
Vadim Sterkin
Не понял, это продолжение обсуждаемой задачи или другая задача? В любом случае, непонятно в чем конкретно она состоит кроме интереса.
Виктор Тарнаев
Вопрос ламера. Это только для 10? Для 7 не подойдет? А то у меня разрослась да 8 с копейками Гб…
Vadim Sterkin
В статье сказано
Решение для 7 тоже есть в статье. Удачи!