Привет Хабр!

В нашей школе мы столкнулись с необходимостью автоматизации установки ПО на компьютеры. Я расскажу, для чего оно нам понадобилось, как я воевал с Windows во время разработки и к чему пришел в конечном итоге.

Как мы к этому пришли?

Те, кто в последние годы сталкивался с Единым государственным экзаменом по информатике, могут знать, что с 2021 года он проводится в компьютерной форме: школьники выполняют различные задания на ПК. Работа с таблицами, языками программирования, текстовыми редакторами — всё это, конечно, хорошо, но это добавляет школам большое количество работы в рамках подготовки к экзаменам. Теперь недостаточно просто распечатать бланки, нужно ещё убедиться, что на компьютерах стоит все нужное ПО нужных версий. И вот тут-то и начинается веселье в виде установки десятка программ на пару сотен компьютеров. И делается это всё в подавляющем большинстве случаев вручную, хотя это можно было бы автоматизировать, чем я и занялся в своей школе, тем более, что на эту автоматизацию у сотрудников был явный запрос.

Мини-дисклеймер перед прочтением:

Да, существует множество способов эту задачу сделать, не изобретая свой велосипед, тем не менее, вместе с реальной практической задачей существовало ещё и желание сделать что-то свое, поэтому не судите строго :)

Чем мы располагаем?

Нам необходимо автоматически ставить программы на Windows, притом желательно, чтобы это происходило не по сети. Почему мы отвергаем сетевую установку? Большинство устройств у нас — ноутбуки, и качество WiFi оставляет желать лучшего. Да и придется все эти устройства как-то идентифицировать в сети + возможно ставить какие-то агенты на сами компьютеры — в общем получается очень много подготовительной работы. Гораздо проще будет ходить с парой флешек, копировать файлы и тыкать по exe'шнику для установки.

Какие готовые решения у нас уже есть? Внимательный читатель к этому моменту уже, скорее всего, просто кричит: «Ansible!» Да, это действительно хороший инструмент, и он даже поддерживает работу с Windows, но, как я уже отметил, сетевая установка нам не подходит.

Другой вариант — Ninite. Это простая программка для автоматической установки некоторого пакета ПО на ту машину, на которой она сама же и запущена. На первый взгляд, то, что надо. Еще и куча серьёзных компаний ее используют, на официальном сайте декларируются: Sony, NASA и другие. Но при ближайшем рассмотрении моментально становится понятен огромный минус — нельзя туда добавить свое ПО, которого еще там нет, например, не получится ставить с помощью нее всякие специфичные программы типа PascalABC или Кумир.

(источник: официальный сайт ninite.com)
(источник: официальный сайт ninite.com)

Есть PSAppDeployToolkit. Но он выглядит и используется примерно так же как и называется. Его будет тяжело сконфигурировать и запустить. Нам же нужно максимальное упрощение — «ткнул, и заработало». Не стоит забывать, что целевая аудитория — школьные учителя. У админа, скорее всего, не будет времени долго разбираться со сложным синтаксисом утилиты, а реальной установкой зачастую занимаются совершенно далекие от ИТ люди.

 (источник: скриншот с рандомного гитхаба с примером проекта, потому что на PSADT нет даже нормальных скринов)
(источник: скриншот с рандомного гитхаба с примером проекта, потому что на PSADT нет даже нормальных скринов)

Наша последняя надежда — PDQ Deploy. Заходим на сайт и тут же уходим: стоит $1500 и не работает с РФ.

Выходит, что нет достаточно простого, но при этом гибкого решения. Поэтому будем писать свое. Как оно будет выглядеть? Далеко за концепцией ходить не будем и возьмём тот же Ansible. У нас будут плейбуки, которые будут выглядеть как-то так:

name: Install software 
tasks:
  - name: Install some program
    std.install:
        name: MyProgram
        publisher: MyPublisher
        version: "1.0.0"
        installer: myprog_installer.exe

Исполняемый файл будет просто выполнять плейбук, лежащий рядом с ним. EXE-файлы установщиков должны быть приложены к плейбуку, либо их можно скачать с помощью std.download — задачи на скачивание файла по URL.

Плейбуки будут состоять из набора последовательных задач. Каждая задача будет иметь критерии исполнения — условия, при которых задача должна исполняться (мы же не хотим ставить уже установленный софт), требования исполнения — валидаторы состояния, позволяющие убедиться, что задачу можно выполнить (например, для задачи на установку это требование наличия админских прав) и, наконец, целевое действие — то, что нужно выполнить. У каждого типа задачи будет свой загрузчик, который будет формировать эти сущности из конфига.

Если б все было так хорошо...

Как только мы доходим до автоматизации установки/удаления ПО на Windows, мы сталкиваемся с большими-большими проблемами.

Во-первых, Windows не имеет никакой стандартизации в этой сфере. И под словом «никакой» я подразумеваю вообще никакой. Некоторые, конечно, могут возразить, что есть msi, Microsoft Store, но будем честны — даже не треть ПО ставится через них. Это тщетные попытки сделать то, что надо было делать несколько десятилетий назад, еще при появлении NT. А сейчас мы имеем целый зоопарк инсталляторов и деинсталляторов: каждая программа ставится как хочет, и что делать с этим — непонятно.

Хотя подождите, у нас же есть Панель управления, а в ней есть список всех установленных программ, значит у нас есть какие-то соглашения, какие-то стандарты? Ну, да, но вообще-то нет.

Как оказывается, Панель управления берёт весь список из реестра, а конкретно из HKEY_LOCAL_MACHINE/SOFTWARE/Microsoft/Windows/CurrentVersion/Uninstall для machine-wide установок, и из соответствующего места в HKEY_CURRENT_USER для per-user установок. И только мы хотим порадоваться, что мы можем хоть на что-то положиться, но все наши розовые мечты разбиваются о суровую реальность. CurrentVersion/Uninstall, конечно, можно использовать как достоверный источник о всех установках на устройстве, за исключением нескольких маленьких «но»:

  • НО нет никаких гарантий, что программа при установке создаст там запись;

  • НО единственным полем, на которое можно рассчитывать, является DisplayName — отображаемое имя программы. Publisher (издатель) может быть пустым (привет, PascalABC.NET). Version может содержать:

    1. реальную версию

    2. какую-то другую версию (Python 3.13.1, но Version=3.13.1150)

    3. быть вообще пустым (снова привет, PascalABC.NET)

    4. может вообще отсуствовать В последнем случае могут еще встречаться поля MajorVersion/MinorVersion. Но и тут никто ничего не гарантирует, потому что они могут называться и VersionMajor/VersionMinor;

  • НО 32-битные программы почему-то подвергнуты сегрегации и располагаются по совсем другим путям.

Или не такие уж это и маленькие «но»… Выходит, что в одном из важнейших мест ОС — списке установленного ПО — происходит полный бардак: никакой валидации схем, никакого порядка — одна анархия.

Ладно, хорошо, с этим можно смириться. Придется, конечно, немного накостылить, поддержать разные схемы, но жить можно. Пока что.

Тихая установка

Во-вторых, следующая проблема — тихая инсталляция / деинсталляция. Если мы будем просто запускать инсталляторы / деинсталляторы, то никакой автоматизации не получится, так как все равно придется тыкать на кнопочки в диалогах. Можно, конечно, прикрутить что-то вроде pyautogui или pywinauto и тыкать на них автоматически, но это не то, чего нам бы хотелось. Хочется чего-то более красивого и лаконичного. И такое есть. Большинство установщиков поддерживают тихую установку, не требующую вообще никаких действий со стороны пользователя: запустил exe'шник с нужным флагом, и готово.

И тут опять мы сталкиваемся с той же проблемой — зоопарком инсталляторов. Каждый инсталлятор имеет свои флаги тихой установки, и это проблема. К счастью, ее за нас уже решили: USSF (Ultimate Silent Switch Finder) позволяет подбирать такие флаги ко всем самым популярным системам установки: MSI, InnoSetup, Nullsoft Install System, InstallShield и кучу всего еще. Путем нехитрого анализа кода утилиты понимаем как она работает и пишем свою реализацию. Поддерживать будем только первые три типа установщиков, если будет надо — расширим. Код максимально прост:

def _is_inno_setup(installer: str) -> bool:
    with open(installer, "rb") as installer_f:
        data = installer_f.read() 
        return b"Inno Setup" in data


def _is_nullsoft_installer(installer: str) -> bool:
    with open(installer, "rb") as installer_f:
        data = installer_f.read()
        return b"Nullsoft Install System" in data


def determine_quiet_install_command(installer: str) -> str | None:
    if installer.endswith(".msi"):
        return f"msiexec /I {installer}" /passive /norestart'
    if _is_inno_setup(installer):
        return f"{installer} /VERYSILENT /SUPPRESSMSGBOXES /NORESTART /SP-"
    if _is_nullsoft_installer(installer):
        return f"{installer} /S"
    return None

Просто ищем текст, который системы сборки оставляют внутри установщиков, и в зависимости от этого используем разные флаги. Таким же образом работает поиск флагов для тихой деинсталляции.

Вот, кстати, еще один смешной момент.
Вы наверняка знаете, что на Windows по умолчанию используются пути с \, в отличие от Linux/Mac с /. Но на Windows можно использовать и обычный слеш: cd C:/Users/User/Documents отработает замечательно. Да и вообще, где угодно можно использовать /, PowerShell поймет все. Или нет? А вот и нет. MSIExec — утилита для установки msi файлов — просто отказывается работать с обычными слешами. То есть утилита, предназначавшаяся для универсальной установки не работает с универсальными слешами. И в этом вся Windows :)

Что в итоге получилось

Спустя десятки часов страданий получается конечный результат. Программку я решил назвать Embark (англ. «производить погрузку на корабль»).

Мы запускаем exe'шник, он ищет все похожие на плейбуки файлы в текущем расположении и предлагает запустить один из них.

стартовый экран
стартовый экран

Затем открывается окно логирования, где отображается весь процесс установки в стиле GitHub Actions.

экран мониторинга процесса выполнения
экран мониторинга процесса выполнения

Если вдруг что-то пойдет не так, то Embark предложит выбрать: прерывать или продолжать работу.

что-то пошло не так :(
что-то пошло не так :(

Естественно, есть не только GUI, но и CLI, а также несколько дополнительных команд для поиска установленного ПО на компьютере, чтобы было проще находить правильные параметры для плейбука (привет проблемам реестра, о которых мы говорили ранее).

запуск плейбука из CLI
запуск плейбука из CLI

Думаю, про стек, использованный в Embark тоже стоит что-то сказать. Все работает на Python 3.13.2. В качестве UI библиотеки используется customtkinter. PyQT/PySide тащить не хотел из-за веса, а customtkinter довольно легкий, красивый и простой. Парсинг конфигов происходит через связку PyYAML + Pydantic.

Организация тестирования

Продукт достаточно серьёзный, будет не очень круто, если он поломается во время использования. Поэтому необходимо обеспечить автоматическое тестирование, чтобы убедиться, что явных проблем у нас нет.

Тесты в основном E2E, так как, пожалуй, во всем проекте немного мест, которые стоило бы тестировать юнит-тестами. В рамках этих E2E тестов проверяются входящие в стандартную поставку типы задач (copy, cmd, install, download). Отдельно проверяется работа интерфейса через скриншотные тесты — запускается окно с тестовыми элементами и скриншот сверяется с эталоном.

Статический анализ тоже имеется: типы проверяются mypy в строгом режиме, чистота кода через Ruff и «самый строгий линтер» wemake-python-styleguide. Для сохранения архитектурных слоев используется import-linter. Он позволяет контролировать зависимости внутри пакета: например, вы можете запретить core компонентам зависеть от плагинов.

Конечно же, нужно настроить CI для автоматического тестирования всего, что попадает в master. Сначала приложение тестируется на наборе тестов с помощью pytest, а затем проходит финальное тестирование, когда приложение собирается в exe-файл и запускается на реальном плейбуке. Здесь проверяется во-первых то, что утилита не упала, а во-вторых, с помощью команды embark dev_query_install выполняется поиск по реестру для того, чтобы убедиться в том, что нужное ПО установилось. Если вдруг что-то отвалится, то пайплайн приложит полную информацию, в том числе и для скриншотных тестов: сделанные скриншоты загрузятся как артефакты, чтобы можно было понять, что пошло не так.

пайплайн на GitHub
пайплайн на GitHub

И что в итоге?

В итоге получилось довольно простое и удобное приложение для автоматизации установки. Мы его внедрили в своей школе, которая как раз в этом году принимает экзамены. Этим самым мы существенно сократили дополнительную нагрузку на сотрудников школы, уменьшили время и увеличили качество подготовки. Если раньше приходилось на одну рабочую станцию тратить по 40 минут, еще и при этом постоянно тыкать там на кнопки, то теперь достаточно за пару минут скопировать плейбук и запустить Embark. Не всегда (в силу специфичности Windows и различных программ) оно работает гладко, конечно, но со своей основной задачей оно справляется, да и я постоянно фикшу баги.

Исходный код проекта, разумеется, на GitHub. Также доступна двуязычная документация.

Спасибо за прочтение!

Сталкивались ли вы с подобными проблемами? Как вам мой проект? Пишите свой опыт и мнение в комментариях!

Комментарии (19)


  1. qark
    15.05.2025 17:50

    В Windows есть стандартный менеджер пакетов - winget. Консольный, но есть и несколько GUI-оболочек.

    Его не хватило бы для этой задачи? winget import кажется тем, что надо.


    1. tapeline Автор
      15.05.2025 17:50

      Насколько я знаю, не-APPX/MSI штуки там поддерживаются так себе.

      The client requires Windows 10 1809 (build 17763) or later at this time

      Есть страшно старые компы (Win 10 1709, если мне не изменяет память), так что придется танцевать с бубном и здесь.

      Плюс кроме установки, нужно еще делать некоторые доп. действия: копировать файлы, ярлыки (ставится все же из админского режима, ярлыки у админа, а должны быть у ученика). Не, разумеется, можно было бы все это подогнать и сделать bat / powershell скрипт, но:

      • было бы это так же удобно?

      • получилась ли бы такая интересная статья?)

      P.s. ну и картинки велосипеда и костыля на КДПВ не просто так были :)


      1. falcon4fun
        15.05.2025 17:50

        Есть страшно старые компы

        Кучу лет было, чтобы обновить весь хлам :D К тому же inplace в 99,99% шикарно встает наверх.


        1. tapeline Автор
          15.05.2025 17:50

          Кучу лет было, чтобы обновить весь хлам

          Так то оно так, но, хочу сказать, что это еще хорошо, что на большинстве стоит +- современная десятка. Нередки случаи, когда в школах стоят семерки до сих пор


          1. falcon4fun
            15.05.2025 17:50

            Ну что сказать. Соболезную. Хотя бы не 2003/XP :D

            Впрочем. 2012r2 серваки тоже нормально in-place обновляется до 22 либо через 2016, либо через 2019. Исключая некоторые нюансы, вроде RDS-а


    1. falcon4fun
      15.05.2025 17:50

      Спустился для этого. Еще есть choco. А еще если зочется все вместе Uniget UI.

      А еще есть Интюн и кастомные пакеты вида intunewin


      1. tapeline Автор
        15.05.2025 17:50

        Еще есть choco

        Придется вручную указывать сайлент-флаги, что не слишком желательно

        А еще есть Интюн и кастомные пакеты вида intunewin

        Интересно, спасибо за рекомендацию, посмотрю!


        1. falcon4fun
          15.05.2025 17:50

          Для NPP, Адоб ридера и прочего, что не в MSI файлах идёт, когда-то делал. В принципе - очень просто. Проблема - поддерживать и обновлять нудно, нужна автоматизация залива в Intune через API и пометка старого, как superseded. В этом плане скорей всего проще WinGet официальную репу найти.

          https://www.recastsoftware.com/resources/create-win32-application-and-deploy-with-microsoft-intune/


          1. tapeline Автор
            15.05.2025 17:50

            В этом плане скорей всего проще WinGet официальную репу найти.

            Как раз в этом и одна из главных проблем: сомневаюсь, что у КуМира и PascalABC.NET есть winget-репа)


  1. alexs89
    15.05.2025 17:50

    Приветствую, коллега. Работаю техником на ЕГЭ. До автоматической установки ПО руки не дошли, т.к. станций немного и расходы времени на автоматизацию превысят расходы времени на ручную установку. Но заморочился с автоматизацией в плане загрузки списка ПО в станцию КЕГЭ. Доработал чужой проект программы для редактирования файлов sft, теперь можно как отредактировать присланный свыше файл под свои реалии, так и создать с нуля. Учитывая ваше количество станций, может быть полезно. https://github.com/Alexvs159/kege_sft_editor


  1. 13werwolf13
    15.05.2025 17:50

    я десяток лет успешно избегаю винды, но даже я знаю что существует sccm..


  1. cdn_crz
    15.05.2025 17:50

    судя по статье - задача была поставить десяток программ проверив их наличие на пк

    на то чтобы проверить ключи тихой установки ушло бы минут 10 (даже если вручную подбирать ключи), на написание start-process -arguments .... еще 2 минуты, а проверку легко можно реализовать проверкой версии целевого exe, расположение которого заведомо известно, реализуется тоже минут за 10-20

    итого данная задача потребовала бы около часа трудозатрат при использовании powershell

    складывается ощущение что у вас было очень много времени на стрельбу из пушки по воробьям

    большой плюс это конечно gui, но через powershell было бы попроще


    1. tapeline Автор
      15.05.2025 17:50

      итого данная задача потребовала бы около часа трудозатрат при использовании powershell

      Несомненно, все эти штуки можно было бы выполнить с помощью батника/повершелл-файла, но заняло бы это времени поболее, часа 3-4, чтобы с этим всем разобраться и заставить работать.

      складывается ощущение что у вас было очень много времени на стрельбу из пушки по воробьям

      В целом, так и есть :). Была не только цель автоматизации, но ещё и (шило в одном месте) желание сделать что-то свое


      1. cdn_crz
        15.05.2025 17:50

        (шило в одном месте) желание сделать что-то свое

        Тогда справедливо


  1. nikerossxp
    15.05.2025 17:50

    Это и правда изобретение велосипеда, когда уже давно есть psexec, который может, например, удалённо запустить батник из сетевой шары, а батник уже поставит всё что надо... Или не батник, а msi. И так далее.


    1. tapeline Автор
      15.05.2025 17:50

      Да, можно сделать с помощью батника, но, как я уже ответил в других комментариях, это было бы сложнее для конечных пользователей и не так интересно в реализации


      1. randomsimplenumber
        15.05.2025 17:50

        Спустя десятки часов страданий

        не так интересно в реализации

        Мужык, признайся, ты же в лес не на охоту ходишь ;) (ц) Медвед

        Костылей для автоматизации администрирования Windows уже придумано чуть более 100500. А с wifi непонятно. Если оно работает - самое медленное wifi всё равно удобнее чем админ с флешкой.


  1. jack_lark
    15.05.2025 17:50

    1. в установке программ для винды нет никакой магии.

    1.1. записать нужные файлы в нужные места

    1.2. записать в registry нужные данные

    1.3. возможно исправить права NTFS и registry.

    1. ansible можно взять из cygwin. sshd оттуда же.


    1. don-Rumata
      15.05.2025 17:50

      1.1. записать нужные файлы в нужные места

      1.2. записать в registry нужные данные

      Всё айти сводится "нажать нужные кнопки в нужном порядке". Почему тогда эти вайтишники такие деньги просят? Там ведь никакой магии.