CI/CD для приложений WinForms / WPF на практике — автоматизация от сборки до подписи и распространения через GitHub Actions
· Го Комура · CI/CD, GitHub Actions, WinForms, WPF, C#, .NET, Подпись кода, MSIX, Deployment, Разработка Windows, Таблица решений
«Релиз можно собрать только на компьютере вот этого человека» — фраза, которую мы действительно часто слышим на консультациях по бизнес-приложениям на WinForms и WPF. Кто-то делает Release-сборку в локальной Visual Studio, упаковывает в zip и кладёт в общую папку. Работает, но никто не может сказать, получится ли выпустить релиз с исправлением бага в тот день, когда этот разработчик окажется в отпуске.
Информации о CI/CD для веб-приложений полно, а стоит перейти к десктопным приложениям — и она резко иссякает. Отчасти это оправданно: «место развёртывания — не сервер, а компьютер клиента» — фундаментальное отличие, из-за которого веб-статью нельзя просто скопировать один в один. Тем не менее автоматизация сборки и тестирования для десктопного приложения требует почти столько же усилий, сколько и для веба. Стена возникает дальше — на подписи и распространении, где практичное решение зависит от формата распространения.
В этом блоге мы уже разбирали выбор формата распространения в статье «Таблица решений по способам распространения Windows-приложений» и подход к подписи в статье «SmartScreen и подпись кода». Опираясь на эти две статьи, здесь мы разберём с практической точки зрения, насколько далеко можно зайти в автоматизации сборки, тестирования, нумерации версий, подписи и создания дистрибутивов приложения WinForms / WPF с помощью GitHub Actions.
1. Сначала вывод
- Главный риск — состояние «собрать можно только на компьютере одного разработчика». Первая цель CI/CD — не полная автоматизация распространения, а воспроизводимость сборки без зависимости от чьего-либо конкретного компьютера.
- Минимальная конфигурация — только автоматизация сборки и тестов — уже сама по себе ценна. Её можно собрать в одном YAML-файле:
windows-latest+actions/checkout+actions/setup-dotnet+dotnet build / test+actions/upload-artifact.12 - WinForms / WPF по умолчанию требуют Windows-раннер. Поскольку они нацелены на предназначенный только для Windows TFM вроде
net8.0-windows,3 запуск тестов в CI требует среды Windows. - Практичное решение для нумерации версий — управление по тегам. Push тега
v1.2.3запускает релизную сборку, а значение из тега передаётся в свойство MSBuildVersion.4 - Подпись — главная стена автоматизации. С июня 2023 года закрытый ключ публичного OV-сертификата обязан храниться на HSM, поэтому прежний стандартный приём «PFX в секрете и signtool» без изменений уже неприменим. Если нужно завершить всё в CI, реалистичный путь — облачный сервис подписи.5
- Простота переноса в CI сильно зависит от формата распространения. По возрастанию сложности: xcopy (zip) — проще всего, MSIX требует обязательной подписи6, MSI работает через CLI-инструмент, а ClickOnce требует
msbuild /target:publishи полон своих особенностей.7 - Не делайте автоматическое UI-тестирование обязательным условием CI. Практичное решение — юнит-тесты обязательны, а UI-тесты ограничены дымовыми проверками в отдельном задании.
2. Чем CI/CD десктопного приложения отличается от веба
Сначала разберём, почему шаблон CI/CD веб-приложения (push → сборка → тесты → деплой на сервер) нельзя перенести без изменений.
| Аспект | Веб-приложение | Десктопное приложение WinForms / WPF |
|---|---|---|
| Место развёртывания | Сервер под собственным управлением | Компьютеры клиента/на местах (вне вашего контроля) |
| Единица распространения | Единовременное переключение на сервере | MSI / MSIX / ClickOnce / zip и другое; момент развёртывания зависит от другой стороны |
| Откат | Можно откатить на сервере | На уже получивших обновление компьютерах откатить непросто. Нужно хранить старые инсталляторы |
| Подпись | Обычно не нужна (TLS на уровне инфраструктуры) | Подпись кода исполняемого файла/пакета практически обязательна |
| Среда сборки | Часто прекрасно работает на Linux-раннере | По умолчанию требует Windows-раннер |
| Тестирование | Часто прекрасно работает без графической сессии | Юнит-тесты такие же; UI-тестам нужна десктопная сессия |
| Смысл «деплоя» | Доведение до продакшена | Зона ответственности CI заканчивается на «дистрибутив готов». Установка — отдельный этап |
Важна последняя строка. Для десктопного приложения выход из конвейера CI/CD — это не «доведение до продакшена», а «подписанный дистрибутив лежит там, откуда его в любой момент можно забрать». Всё, что дальше (развёртывание у клиента, автообновление), — вопрос проектирования распространения, разобранный в статье «Таблица решений по способам распространения». Если же смотреть с другой стороны — если так определить точку выхода, CI/CD для десктопного приложения можно собрать теми же инструментами, что и для веба.
3. Минимальная конфигурация — сборка и тесты в GitHub Actions
Вот единственное, что стоит подключить в первую очередь. Поскольку размещаемые на GitHub раннеры получают под каждое задание новую виртуальную машину,1 при каждом push сборка и тесты запускаются на чистой Windows, и зависимость от «SDK, установленного только на компьютере того человека» сразу же обнаруживается.
name: build-and-test
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
jobs:
build:
runs-on: windows-latest # WinForms / WPF требуют Windows-раннер
steps:
- uses: actions/checkout@v4
- uses: actions/setup-dotnet@v4
with:
dotnet-version: '8.0.x'
- name: Restore
run: dotnet restore
- name: Build
run: dotnet build --configuration Release --no-restore
- name: Test
run: dotnet test --configuration Release --no-build
- name: Publish
run: dotnet publish src/MyApp/MyApp.csproj -c Release -o publish
- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: MyApp
path: publish
Уточним три момента.
Во-первых, базовый вариант — runs-on: windows-latest. Проект WinForms / WPF имеет TargetFramework, представляющий собой предназначенный только для Windows TFM вроде net8.0-windows, и является проектом на .NET desktop SDK с включённым UseWindowsForms или UseWPF.3 Строго говоря, если требуется только компиляция, собрать можно и на Linux-раннере, включив EnableWindowsTargeting, но этап, включающий реальное выполнение, вроде dotnet test, требует среды Windows, поэтому в такой конфигурации, где тесты запускаются в том же задании, естественно использовать именно Windows-раннер. Для .NET Framework 4.x (csproj старого формата) вместо dotnet build используются MSBuild и NuGet CLI, но оба предустановлены на Windows-раннере, а подход остаётся тем же.
Во-вторых, обязательно сохраняйте результат через actions/upload-artifact. «Полный набор результатов данной сборки можно забрать из GitHub» — это и есть на практике избавление от привязки к конкретному компьютеру. Даже срочная сборка для проверки работоспособности превращается в простое скачивание zip-архива с экрана Actions.
В-третьих, на этом этапе подпись и распространение ещё не выполняются. Уже одна эта минимальная конфигурация даёт две гарантии — «main всегда можно собрать и протестировать» и «любой может забрать один и тот же результат сборки», — и, по моему опыту, это снимает бо́льшую часть проблем небольшой команды.
4. Автоматическая нумерация версий — релизы по тегам
Следующий этап — решение проблемы «а какая версия у этого zip?». При практике локальных сборок классическая неприятность — забыть поменять Version в csproj, из-за чего одна и та же версия 1.0.0 существует уже много поколений подряд. Практичное решение — релизы, управляемые тегами: на коммит, который нужно выпустить, ставится тег вроде v1.2.3, это запускает workflow, а версия, взятая из имени тега, передаётся в сборку.
name: release
on:
push:
tags: [ 'v*' ]
jobs:
release:
runs-on: windows-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-dotnet@v4
with:
dotnet-version: '8.0.x'
# Push тега не запускает workflow сборки+тестов из раздела 3,
# поэтому перед созданием релизного артефакта тесты прогоняются и здесь
- name: Test
run: dotnet test --configuration Release
- name: Publish with version from tag
shell: pwsh
run: |
$version = $env:GITHUB_REF_NAME.TrimStart('v') # v1.2.3 -> 1.2.3
dotnet publish src/MyApp/MyApp.csproj `
-c Release -o publish `
-p:Version=$version
- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: MyApp-${{ github.ref_name }}
path: publish
Передав значение как свойство MSBuild вроде -p:Version=1.2.3, в проекте .NET SDK по умолчанию AssemblyVersion и FileVersion генерируются из префикса Version (части без суффикса), а InformationalVersion — из самого Version.4 В csproj остаётся только временное значение для разработки, а официальная версия релиза хранится централизованно, единственным источником — тегом.
Есть один момент, который стоит учесть. У артефактов actions/upload-artifact есть срок хранения на уровне репозитория (по умолчанию 90 дней), и по истечении срока они удаляются. Поскольку десктопному приложению для отката нужно долгое хранение старых инсталляторов, артефакты из тегированной сборки стоит публиковать в постоянное место, например прикреплять к GitHub Release, а артефакт Actions рассматривать как исключительно временную передачу.
Преимущество в том, что вся эксплуатация остаётся внутри Git. «Какому коммиту соответствует версия 1.2.3 в окружении клиента» однозначно определяется тегом, и версия файла, отображаемая в свойствах EXE, механически совпадает с тегом Git. Более того, начиная с .NET 8 SDK, к InformationalVersion по умолчанию добавляется хэш коммита Git (SourceRevisionId),4 и если вывести это на экран версии приложения, можно напрямую определить нужный коммит по артефакту сборки.
5. Встраивание подписи кода в CI — здесь главная стена
Команды, которым удалось гладко автоматизировать сборку и версионирование, почти неизбежно останавливаются именно на подписи. Причины, по которым подпись кода фактически обязательна для Windows-приложения, распространяемого вне Store (SmartScreen, корпоративные средства защиты, обнаружение подделки), мы разобрали в статье «SmartScreen и подпись кода», поэтому здесь сосредоточимся только на том, где именно в CI это выполнять и как.
5.1. Базовая форма signtool
Само выполнение подписи — это одна команда. signtool входит в состав Windows SDK и доступен и на Windows-раннерах GitHub. В текущих версиях SDK обязательны параметры /fd (дайджест файла) и /td (дайджест метки времени), рекомендуется SHA256.8
signtool sign /f MyCert.pfx /p $env:PFX_PASSWORD `
/fd SHA256 /tr http://timestamp.digicert.com /td SHA256 `
publish\MyApp.exe
Метка времени (/tr) технически необязательна, но добавляйте её всегда. С меткой времени можно подтвердить, что «на момент подписи сертификат был действителен», даже после истечения срока действия сертификата, и подпись на уже распространённых файлах остаётся действительной.86
5.2. Типы сертификатов и реальность встраивания в CI
Проблема не в команде, а в том, где хранится закрытый ключ. Способ получения сертификата принципиально меняет способ встраивания в CI.
| Форма сертификата | Расположение закрытого ключа | Встраивание в CI | Примечание |
|---|---|---|---|
| Облачный сервис подписи (Azure Artifact Signing = бывший Trusted Signing и т. п.) | На стороне облака | Встраивается легко (основной вариант). Изначально спроектирован для интеграции с GitHub Actions и подобным | Ограничение по доступным странам/регионам (для юрлиц — США, Канада, ЕС, Великобритания и т. п.)5 |
| OV-сертификат (выпущенный впервые начиная с июня 2023 года) | Обязателен HSM / USB-токен | Токен нельзя вставить в раннер, поэтому напрямую невозможно. Возможно через опцию облачного HSM от CA | По требованиям CA/Browser Forum5 |
| EV-сертификат | HSM / USB-токен | То же самое | Эффект мгновенного доверия SmartScreen отменён в 2024 году. С точки зрения эксплуатации подписи рассматривайте наравне с OV5 |
| Устаревший PFX-файл (выпущенный ранее, внутренний CA, самоподписанный) | Файл | Хранить в секрете в Base64 и восстанавливать (см. ниже) | Для новой публичной публикации в этой форме сертификат, как правило, уже не получить |
То есть конфигурация «PFX в секрете GitHub и подпись через signtool», которая часто встречается в поиске, всё ещё действительна для внутреннего CA или существующего PFX, но для тех, кто будет получать публичный сертификат впредь, эта предпосылка больше не работает. Настраивая всё с нуля, реалистичнее рассматривать в первую очередь облачный сервис подписи, изначально поддерживающий интеграцию с CI.5 Если вы продолжаете работать с USB-токеном, получается гибридная конфигурация, где только этап подписи остаётся на локальном компьютере или на self-hosted раннере со вставленным токеном.
5.3. Особенности управления секретами
Стандартные приёмы для переноса в CI подхода с PFX (внутренний CA, существующий сертификат).
- Храните PFX как строку Base64 в секрете GitHub и восстанавливайте в файл внутри задания. Это процедура, которую рекомендует сама документация GitHub для работы с бинарными данными как секретом.9
- Пароль храните в отдельном секрете. Значения секретов автоматически маскируются в логах,9 но эта маскировка не распространяется на производные значения, полученные после обработки. Не передавайте их как переменные окружения ничему, кроме этапа подписи.
- В pull request из форков секреты не передаются (кроме
GITHUB_TOKEN).9 Однако само задание всё равно запускается с пустыми секретами, поэтому упомянутый выше этап восстановления завершится ошибкой при попытке декодировать Base64 из пустой строки. Либо держите этап подписи изолированным в релизном workflow, запускаемом по тегу (как в разделе 4, который не срабатывает на PR из форка), либо добавьте явное условие вродеif: github.event_name != 'pull_request', чтобы намеренно его пропускать.
- name: Restore signing certificate
shell: pwsh
run: |
$bytes = [Convert]::FromBase64String($env:PFX_BASE64)
[IO.File]::WriteAllBytes("$env:RUNNER_TEMP\sign.pfx", $bytes)
env:
PFX_BASE64: ${{ secrets.SIGNING_PFX_BASE64 }}
Ещё один критичный момент — ограничить набор workflow, у которых есть доступ к ключу подписи. Любой, у кого есть право записи в репозиторий, может изменить workflow, поэтому секрет подписи стоит хранить в Environment с обязательными утверждающими, чтобы к нему мог обращаться только релизный workflow. Подписанный бинарник сам по себе служит доказательством «это создала наша компания», поэтому обращаться с ключом стоит с той же строгостью границы доверия, что и с инфраструктурой доставки автообновлений (по этому поводу см. также «Безопасность автообновления»).
6. Таблица решений по интеграции CI/CD для каждого формата распространения
Когда с подписью разобрались, остаётся последнее — форма самого дистрибутива. Выбор формата как таковой оставим статье «Таблица решений по способам распространения», а здесь сравним исключительно с точки зрения CI/CD.
| Формат распространения | Простота сборки в CI | Способ сборки в CI | Требования к подписи | Автообновление |
|---|---|---|---|---|
| xcopy (распространение zip) | Проще всего | Только dotnet publish + архивация |
Подпись EXE/DLL (рекомендуется) | Нет (ручное развёртывание) |
| xcopy + самописный апдейтер | Просто (сама сборка). Проектирование доставки обновлений — отдельная тяжёлая задача | dotnet publish + генерация манифеста |
Подпись EXE + обязательное проектирование проверки файлов обновления | Самописное (нужно проектировать границу доверия) |
| MSI | Средне | Запуск такого инструмента, как WiX, через CLI | Подпись MSI-файла (рекомендуется, местами фактически обязательна) | Нет (нужен отдельный механизм распространения) |
| MSIX | Средне | MSBuild / MakeAppx + signtool | Подпись пакета обязательна (неподписанный пакет установить нельзя)6 | Можно реализовать через App Installer и т. п. |
| ClickOnce | Полон своих особенностей | msbuild /target:publish + профиль публикации (не поддерживается через dotnet CLI)7 |
Подпись манифеста + подпись EXE | Встроено (главное назначение формата) |
Дополним.
- xcopy (zip): workflow из разделов 3 и 4 практически представляет собой готовую форму. Каким бы ни оказался итоговый формат распространения, добиться сначала именно этого — кратчайший путь.
- MSI: определение инсталлятора (WiX и т. п.) включается в репозиторий и собирается через CLI. Суть здесь не столько в самой генерации, сколько в проектировании «что включать в MSI» (регистрация службы, per-machine/per-user).
- MSIX: поскольку Windows не разрешает установку неподписанного MSIX, автоматизация CI не считается завершённой без автоматизированной подписи в комплекте.6 С другой стороны, если инфраструктура подписи уже настроена, это формат, который легко переносится в CI. Есть и альтернатива: при распространении через Microsoft Store переподпись выполняет сам Store, и собственный сертификат вообще не нужен.5
- ClickOnce: опубликовать через dotnet CLI нельзя — нужен
msbuild /target:publish /p:PublishProfile=...с указанным профилем публикации (.pubxml). Номер редакции (ApplicationRevision), который в IDE увеличивается автоматически при каждой публикации, из командной строки не увеличивается,7 поэтому явная передача версии через управление тегами из раздела 4 становится обязательной. При этом стоит учесть: проверка обновления в ClickOnce основана не на-p:Version(информации о сборке), а на версии со стороны развёртывания (ApplicationVersion/ApplicationRevision), поэтому если не передать отдельно четырёхчастное значение, построенное из тега, например/p:ApplicationVersion=1.2.3.0, новый релиз не будет распознан как обновление. Механизм и применимость разобраны в статье «Что такое ClickOnce».
Если смотреть исключительно с точки зрения CI/CD, путь с наименьшими лишними затратами — «начать с zip, а когда требования к распространению определятся, добавить MSIX или MSI отдельным заданием». Предыдущие этапы (сборка, тесты, версионирование) общие для всех форматов, поэтому замена этапа распространения позже не обесценивает уже вложенные усилия.
7. Насколько далеко заходить с автоматизацией тестирования
Напоследок — где провести границу того, какое тестирование делать обязательным условием CI (обязательной проверкой).
| Уровень теста | Обращение в CI | Причина |
|---|---|---|
| Юнит-тесты (логика) | Обязательное условие. Запускается на каждый pull request | Быстро, стабильно, работает как есть на Windows-раннере |
| Интеграционные тесты без экрана (БД, файловый ввод-вывод) | В принципе обязательны; при медлительности выносить в ночной прогон | Инициализация внешних зависимостей требует усилий, но окупаемость автоматизации высока |
| Автоматические UI-тесты (дым) | Небольшое число, в отдельном задании | Требует десктопной сессии и содержит много источников нестабильности |
| Автоматические UI-тесты (полное покрытие) | Не делать обязательным условием CI | Стоимость поддержки обычно перевешивает выгоду |
Для десктопного приложения наивысшую окупаемость даёт автоматизация не UI, а того, что под ним. Если бизнес-логика зарыта в code-behind, юнит-тесты для неё не написать, поэтому отделение логики от экрана само по себе становится предварительной инвестицией в CI/CD. Практичное решение — ограничить автоматические UI-тесты дымовой проверкой «запустился, залогинился, открылись основные экраны» и запускать её отдельным триггером, например по ночам. UI-тесты на раннере содержат немало ловушек, связанных с сессией экрана, разрешением и таймингом; эта область, включая подводные камни CI и безнадзорного выполнения, разобрана в статье «Автоматизированное UI-тестирование desktop-приложений для Windows».
8. Итог
- CI/CD десктопного приложения можно собрать теми же инструментами, что и для веба, если определить точку выхода как «готов подписанный дистрибутив».
- Минимальная конфигурация —
windows-latest+actions/checkout+actions/setup-dotnet+dotnet build / test+actions/upload-artifact. Уже этого достаточно, чтобы устранить риск «собрать можно только на компьютере одного разработчика».12 - WinForms / WPF по умолчанию требуют Windows-раннер, поскольку нацелены на предназначенный только для Windows TFM вроде
net8.0-windows.3 - Версию централизуйте через управление тегами: тег
v1.2.3→ передача в-p:Version.4 - Подпись — главная стена автоматизации CI. Теперь, когда и OV-сертификаты обязаны храниться на HSM, для полного завершения в CI основной вариант — облачный сервис подписи, а подход PFX+секрет годится для внутреннего CA или уже существующего сертификата.59
- Простота переноса формата распространения в CI возрастает в порядке: xcopy (zip) → MSI / MSIX → ClickOnce. MSIX требует обязательной подписи6, а ClickOnce требует внимания к
msbuild /target:publishи неавтоматическому увеличению номера редакции.7 - Сделайте юнит-тесты обязательным условием CI, ограничьте UI-тесты дымовыми проверками в отдельном задании и рассматривайте отделение логики от экрана как предварительную инвестицию.
Похожие статьи
- Как выбрать способ распространения Windows-приложения — таблица решений MSI / MSIX / ClickOnce / xcopy / собственный updater
- Почему в Windows появляется «Windows защитила ваш компьютер» — SmartScreen и подпись кода
- Что такое ClickOnce — механизм, обновления и случаи применимости с практической точки зрения
- Автообновление — это граница доверия: почему одного HTTPS недостаточно
- Автоматизированное UI-тестирование desktop-приложений для Windows
Смежные области консультаций
В Komura Software LLC мы занимаемся разработкой приложений WinForms / WPF, переводом команд с локальных сборок на CI/CD, проектированием конвейеров сборки, подписи и распространения на GitHub Actions, а также подготовкой существующих десктопных приложений к тестированию через отделение логики.
- Разработка приложений для Windows
- Доработка и обслуживание существующего ПО для Windows
- Техническая консультация и ревью архитектуры
- Контакты
Справочные материалы
-
GitHub Docs, GitHub-hosted runners reference. О метках раннеров вроде
windows-latest, о том, что каждому заданию выделяется новая виртуальная машина, и о бесплатности стандартных раннеров на публичных репозиториях. ↩ ↩2 ↩3 -
Microsoft Learn, GitHub Actions and .NET. О CI/CD для .NET через GitHub Actions, о роли actions/checkout и actions/setup-dotnet, а также об использовании dotnet restore / build / test / publish внутри workflow. ↩ ↩2
-
Microsoft Learn, MSBuild reference for .NET Desktop SDK projects. О том, что проекты WinForms / WPF указывают специфичный для Windows TFM вроде
net8.0-windows, и о включении .NET desktop SDK черезUseWindowsForms/UseWPF. ↩ ↩2 ↩3 -
Microsoft Learn, Set assembly attributes in a project file. О том, что
AssemblyVersion/FileVersion(без суффикса) иInformationalVersionпо умолчанию генерируются из свойстваVersion, и о том, что начиная с .NET 8 SDK кInformationalVersionдобавляетсяSourceRevisionId(хэш коммита). ↩ ↩2 ↩3 ↩4 -
Microsoft Learn, Code signing options for Windows app developers. О том, что с июня 2023 года требования CA/Browser Forum обязали хранить закрытый ключ OV-сертификата на HSM/аппаратном токене, об отмене в 2024 году обхода первичного SmartScreen для EV-сертификатов, о том, что Azure Artifact Signing (бывший Trusted Signing) не требует токена и интегрируется с GitHub Actions и подобным, но ограничен по регионам, а также о переподписи Microsoft MSIX-пакетов, распространяемых через Store. ↩ ↩2 ↩3 ↩4 ↩5 ↩6 ↩7
-
Microsoft Learn, Sign an MSIX package. О том, что Windows требует действительную подпись кода для MSIX-пакета, и о том, что метка времени сохраняет действительность проверки подписи даже после истечения срока сертификата. ↩ ↩2 ↩3 ↩4 ↩5
-
Microsoft Learn, Build .NET ClickOnce applications from the command line. О том, что публикация ClickOnce в .NET требует
msbuild /target:publishс указанным профилем публикации, и о том, чтоApplicationRevisionне увеличивается автоматически при сборке из командной строки. ↩ ↩2 ↩3 ↩4 -
Microsoft Learn, SignTool. О том, что SignTool входит в состав Windows SDK, что в текущих сборках обязательны
/fdи/tdс рекомендуемым SHA256, и об указании метки времени RFC 3161 через/tr. ↩ ↩2 -
GitHub Docs, Using secrets in GitHub Actions. Об автоматическом сокрытии значений секретов в логах, о процедуре хранения бинарных данных вроде сертификатов в Base64 в секрете и восстановления их внутри задания, а также о том, что секреты не передаются в workflow, запущенные из форков. ↩ ↩2 ↩3 ↩4
Похожие статьи
Недавние статьи с теми же тегами помогут подробнее изучить близкие темы.
Автоматическое UI-тестирование десктопных приложений Windows — как устроен UI Automation и как строить устойчивые тесты на FlaUI
Разбираем автоматическое UI-тестирование WinForms/WPF-приложений от основ Windows UI Automation. Минимальная реализация на FlaUI, проекти...
Зачем использовать .NET Generic Host и BackgroundService в десктопных приложениях
Разбираем, как использовать Generic Host и BackgroundService, чтобы упорядочить запуск, периодическую обработку, завершение работы, логир...
Версионирование схемы БД бизнес-приложения — практика миграций, предотвращающая «у каждого клиента своя база»
Практическое руководство по версионированию схемы БД бизнес-приложения, установленного у множества клиентов. Разбираем PRAGMA user_versio...
Если ваше Windows-приложение приняли за вирус — как реагировать на ложные срабатывания Microsoft Defender и жить с влиянием на производительность
Разбираем правильный порядок действий, если Microsoft Defender ложно определяет ваше Windows-приложение как вредоносное: как устроена сов...
Значки в области уведомлений и всплывающие (toast) уведомления в Windows-приложениях — подводные камни NotifyIcon и выбор правильного AppNotification
Практическое руководство о том, как удерживать бизнес-приложение Windows в области уведомлений (system tray) и оповещать пользователя с п...
Связанные темы
Эти страницы показывают тему статьи в более широком контексте услуг и решений.
Технические темы Windows
Раздел о разработке Windows, расследовании сбоев и использовании существующих активов.
Поток UI и таймеры
Поток UI WPF / WinForms, асинхронные операции, Dispatcher и проектирование таймеров.
Услуги по этой теме
Статья напрямую связана со следующими услугами.
Разработка приложений для Windows
Бизнес-приложения, интеграция оборудования и средства связи — от требований до разработки.
Сопровождение и модернизация ПО Windows
Безопасно добавляем функции, сопровождаем и поэтапно модернизируем существующее ПО Windows.
Частые вопросы
Вопросы, которые часто возникают при консультациях по теме статьи.
- На каком раннере GitHub Actions запускать сборку приложения WinForms / WPF?
- Используйте Windows-раннер, например windows-latest. Проекты WinForms / WPF нацелены на предназначенный только для Windows целевой фреймворк вроде net8.0-windows, и для проверки результата сборки, и для запуска тестов через dotnet test нужна среда Windows. Поскольку размещаемые на GitHub раннеры получают новую виртуальную машину под каждое задание, вы получаете воспроизводимые сборки, не зависящие от конкретного компьютера разработчика. На публичных репозиториях стандартные раннеры бесплатны, на приватных — тарифицируются по минутам.
- Можно ли полностью автоматизировать подпись кода в CI?
- Зависит от того, как хранится сертификат. Прежний подход — положить PFX-файл в секрет и подписывать через signtool — как правило, уже нельзя использовать для новых сертификатов, поскольку с июня 2023 года требования CA/Browser Forum обязали хранить закрытый ключ публичного OV-сертификата на HSM (аппаратном модуле). Сертификат на USB-токене нельзя вставить в облачный раннер, поэтому для полной автоматизации в CI реалистичный путь — облачный сервис подписи вроде Azure Artifact Signing (бывший Trusted Signing) или облачный HSM, предоставляемый CA. Если продолжаете работать с токеном, сам этап подписи придётся оставить на локальной машине или на self-hosted раннере.
- С чего начать автоматизацию в первую очередь?
- Начните только с автоматизации сборки и тестов. Уже одно то, что при каждом push на раннере windows-latest запускаются dotnet build / dotnet test, устраняет главный риск: «собрать можно только на одном компьютере разработчика» и «о том, что мердж сломал сборку, узнают только перед самым релизом». Автоматизацию подписи, создания инсталлятора и распространения можно добавлять поэтапно позже — попытка сразу настроить всё целиком обычно упирается именно в подпись.
- Меняется ли простота настройки CI/CD в зависимости от формата распространения (MSI / MSIX / ClickOnce / xcopy)?
- Меняется, и значительно. Распространение через xcopy (zip) проще всего, поскольку это просто архивация результата dotnet publish. MSIX можно встроить в CI через MSBuild и signtool, но подпись пакета обязательна. MSI автоматизируется вызовом из CI такого инструмента, как WiX. ClickOnce нельзя опубликовать через dotnet CLI вообще — нужна связка msbuild /target:publish и профиля публикации, и стоит учитывать, что номер редакции не увеличивается автоматически из командной строки. Простоту переноса в CI стоит учитывать уже на этапе выбора формата распространения.
Об авторе
Страница с профилем автора статьи.
Го Комура
Представитель KomuraSoft LLC
Специализируется на разработке программного обеспечения для Windows, техническом консалтинге и расследовании сбоев, особенно в проектах с унаследованными системами и трудно воспроизводимыми ошибками.
Публичные ссылки