Автоматизация настройки ПК с помощью winget + PowerShell ── делаем регламент исполняемым

· Обновлено: · · winget, PowerShell, Windows, Настройка ПК, Информационные системы, Автоматизация, Улучшение эксплуатации, Эффективность бизнеса

Каждый раз, когда приходит новый сотрудник или меняется компьютер, кто-то берёт регламент и настраивает машины по одной. В ИТ-отделе небольшой или средней компании проблема не только в затраченных часах. Ручная работа порождает разброс в настройках, а из-за устаревших регламентов и смены ответственных остаются неприятности вида «только на этой машине настройки другие».

Чтобы этого стало меньше, замените регламент на конфигурационные файлы и скрипты, которые можно запускать повторно. Установку приложений оставьте пакетному менеджеру Windows winget, для частей, которыми хочется управлять декларативно, используйте WinGet Configuration, а настройки, которые не покрывает ни то, ни другое, допишите на PowerShell.

Первое, что нужно разделить при проектировании, — «что устанавливать» и «от чьего имени настраивать». Даже если приложения установлены, настройка не завершена, пока пользователь не видит диски и принтеры. В статье последовательно разбираются основы установки приложений, затем реализация с разделением на фазу администратора и фазу пользователя и, наконец, проверки для запуска без участия оператора.

Отталкивайтесь от своей проблемы

Что нужно проверить Что усвоить в первую очередь Где читать
Установка зависает в ожидании согласия Разделить целевой ID, запуск без оператора и согласие с условиями использования Глава 2: установка без оператора
Нужно установить для всех пользователей Есть ли установщик с поддержкой области machine Глава 2: проверка области
Нужно перенести конфигурацию эталонного ПК на новый ПК Воспроизведение списка пакетов и перенос настроек — разные задачи Глава 3: export / import
Неясно, использовать YAML или собственный скрипт Что покрывает каждый из них и как не дублировать список приложений Глава 4: Configuration, Раздел 4.2: разделение ролей
Хочется понять, что менять в длинном примере JSON конфигурации и два скрипта, работающих с разными правами Глава 5: общая картина, Ключи конфигурации
Подключённые диски и принтеры не видны Настраивать без повышения прав, при входе пользователя Фаза пользователя, Способ запуска
Нужно предусмотреть повторный запуск после сбоя Текущее состояние, проверка после установки, журнал, коды возврата Фаза администратора, Коды возврата, Проверки при проектировании
Хочется запускать из SYSTEM или из средства развёртывания Проверка под реальной учётной записью запуска Глава 6: модуль PowerShell, Глава 7: запуск без оператора
Нужно проверить распространяемые файлы и итоговые решения Условия применимости примера и значения для своей организации Глава 8: таблица решений, Примеры кода

Если читаете подряд — начинайте с главы 1. Если нужна сразу реализация — переходите к трёхчастной структуре в главе 5.

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

Статья построена на материале, впервые опубликованном в июле 2026 года и обновлённом 2 августа. Требования инструментов и ограничения системного контекста сверяйте с той версией, которую разворачиваете, и с учётной записью, под которой запускаете. Дата обновления статьи и проверка в вашем целевом окружении — разные вещи.

1. Сначала выводы

Отделите установку приложений от воспроизведения настроек

  • Установку приложений оставьте winget. winget install поддерживает тихую установку по умолчанию.1
  • Конфигурацию существующего ПК можно выгрузить командой winget export. Но только для пакетов под управлением winget, и настройки в неё не входят.2

    Выберите способ управления конфигурацией

  • Если нужен декларативный подход, используйте WinGet Configuration (winget configure). Он основан на PowerShell DSC и позволяет описать приложения и настройки в одном YAML-файле. Требования — Windows 10 1809 или более поздняя версия + winget 1.6 или более поздний.3
  • То, чего winget не покрывает, допишите на PowerShell. Принтеры, общие диски, реестр, компоненты Windows, локальные учётные записи и так далее.
  • Для работы из PowerShell есть модуль Microsoft.WinGet.Client. Он предоставляет командлеты вроде Install-WinGetPackage.4

    Спроектируйте учётную запись запуска и повторные запуски

  • Выполнение в системном контексте (запуск под учётной записью, отличной от вошедшего пользователя, например под SYSTEM) требует осторожности. Microsoft пока указывает его как пункт будущей разработки, поэтому проверка под реальной учётной записью запуска обязательна.3
  • Обязательно обеспечивайте идемпотентность (одинаковый результат при любом числе запусков). Настройка срывается на середине, и её должно быть можно перезапускать сколько угодно раз.
  • Не пытайтесь насильно затащить внутренние приложения в winget — реалистичнее тихо запускать установщик из PowerShell.

На схеме сплошная линия обозначает отношение, которое выполняется всегда, а пунктирная — условное отношение (условия указаны в пояснении к каждому отношению на странице сведений). Полный список отношений (всего 20, с доказательствами и степенью уверенности) и определения основных понятий собраны на странице сведений карты знаний (на японском). Данные: JSON-LD / Turtle

2. Основы winget — как писать установку без оператора

Укажите целевой ID, запуск без оператора и согласия

Начнём с шаблона, который обеспечивает надёжную установку без единого взаимодействия.1

# Указываем ID точно и устанавливаем (-e — точное совпадение, --id задаёт ID)
#   --silent                     : не показывать интерфейс
#   --accept-package-agreements  : согласие с условиями использования пакета
#   --accept-source-agreements   : согласие с условиями использования источника
#   --scope machine              : установка для всех пользователей (только для поддерживающих пакетов)
# Обратный апостроф переноса строки ставится в конце строки. Комментарий после него перенос отменяет
winget install --id Google.Chrome -e --silent `
    --accept-package-agreements --accept-source-agreements --scope machine

# Узнать ID
winget search "Visual Studio Code"
winget show --id Microsoft.VisualStudioCode

Забудете про --accept-* — и запуск без оператора зависнет в ожидании согласия. Именно на этом спотыкаются в первую очередь при автоматизации настройки.

Сначала проверьте, поддерживается ли установка для всех пользователей

--scope machine подходит не всем пакетам: некоторые приложения можно установить только для отдельного пользователя. В таком случае настройте их запуск в контексте пользователя при первом входе в систему.

Поддержку можно проверить до установки. winget show принимает --scope, поэтому заранее видно, есть ли установщик для области machine.5

# Проверить, есть ли установщик для области machine
winget show --id Google.Chrome -e --scope machine

# Для сравнения посмотреть и область user
winget show --id Google.Chrome -e --scope user

Если установщика, соответствующего указанной области, нет, выводится сообщение об этом. Прогоните эту проверку по всем целевым пакетам до того, как впишете "scope": "machine" в файл конфигурации, — и вы не столкнётесь с ошибкой впервые в середине запуска без оператора.

3. Выгрузка конфигурации текущего ПК — export / import

Сначала выгрузите список пакетов

Если у вас уже есть настроенный «эталонный ПК», его конфигурацию можно превратить в файл.2

# Выгрузить список установленных пакетов с эталонного ПК в JSON
winget export --output D:\kitting\apps.json --include-versions

# Восстановить его на новом ПК
winget import --import-file D:\kitting\apps.json `
    --accept-package-agreements --accept-source-agreements --ignore-unavailable

Полученный JSON имеет такую структуру (значения приведены как пример).2

{
  "CreationDate": "2026-07-25T10:40:00.000-00:00",
  "Sources": [
    {
      "Packages": [
        { "PackageIdentifier": "Google.Chrome", "Version": "126.0.6478.127" },
        { "PackageIdentifier": "Microsoft.VisualStudioCode", "Version": "1.101.2" },
        { "PackageIdentifier": "7zip.7zip", "Version": "24.09" }
      ],
      "SourceDetails": {
        "Argument": "https://cdn.winget.microsoft.com/cache",
        "Identifier": "Microsoft.Winget.Source_8wekyb3d8bbwe",
        "Name": "winget",
        "Type": "Microsoft.PreIndexed.Package"
      }
    }
  ],
  "WinGetVersion": "1.9.25180"
}

Что попадает в JSON, а что настраивается отдельно

Как видно, содержимое — это список идентификаторов пакетов и версий (без --include-versions поля Version нет, и import устанавливает последнюю версию).2 Иначе говоря, winget import делает только «установи пакет с таким идентификатором» — ни настройки приложений, ни приложения, установленные не через winget, сюда не попадают ни одним символом. Откройте JSON сами, и это ограничение станет конкретным.

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

Что умеет Что не умеет
Воспроизводить список пакетов под управлением winget Переносить настройки внутри приложений
Фиксировать версии (--include-versions) Приложения, установленные не через winget, и внутренние приложения
Пропускать отсутствующие пакеты (--ignore-unavailable) Активация лицензий и состояние входа в систему

То есть winget import — это отправная точка, а не вся настройка. Как заполнить остальное — и есть основная тема.

4. Пишем декларативно — WinGet Configuration

Пишите желаемое состояние, а не процедуру

winget configure — механизм, который объявляет в YAML состояние, в котором система должна в итоге оказаться, и применяет его через PowerShell DSC.3 По сравнению с процедурным скриптом у него три преимущества.

  • Если желаемое состояние уже достигнуто, он ничего не делает (идемпотентность)
  • Установку приложений, настройки Windows и настройки приложений можно выразить в одном файле
  • Если на середине произошёл сбой, достаточно повторно запустить тот же файл

В примере ниже показана структура. Прежде чем делать файл для своей организации, посмотрите в разделе 4.1, что означает allowPrerelease и почему важно согласовывать значение в пределах одного типа ресурса.

# kitting.winget — объявляем желаемое состояние эталонной машины
properties:
  configurationVersion: 0.2.0
  resources:
    - resource: Microsoft.WinGet.DSC/WinGetPackage
      id: chrome
      directives:
        description: Установить Google Chrome
        allowPrerelease: true
      settings:
        id: Google.Chrome
        source: winget

    - resource: Microsoft.WinGet.DSC/WinGetPackage
      id: vscode
      directives:
        description: Установить Visual Studio Code
      settings:
        id: Microsoft.VisualStudioCode
        source: winget

    - resource: Microsoft.Windows.Developer/DeveloperMode
      id: devmode
      directives:
        description: Включить режим разработчика (только для машин разработчиков)
        allowPrerelease: true
      settings:
        Ensure: Present
# Посмотреть содержимое перед применением (показывает, что будет выполнено)
winget configure show --file D:\kitting\kitting.winget

# Применить
winget configure --file D:\kitting\kitting.winget --accept-configuration-agreements

Проверьте требования целевой системы

Требования: Windows 10 версии 1809 (сборка 17763) или более поздняя либо Windows 11, и winget 1.6.2631 или более поздний.3 В окружениях, где остаются старые машины, надёжнее конфигурация на PowerShell из следующей главы.

4.1. Как читать directivesallowPrerelease не про приложение

Приложение задаётся в settings, указания по ресурсу — в directives

directives — то место, где неизбежно возникает путаница, когда YAML собирают из чужих примеров. Здесь пишутся указания о том, как обращаться с ресурсом, а не спецификация устанавливаемого приложения (она находится на стороне settings).

  • description — текст описания, отображаемый при запуске. Благодаря ему вывод winget configure show читается как есть, поэтому пишите его всегда
  • allowPrerelease — указание, которое разрешает использовать предварительную версию модуля PowerShell, предоставляющего DSC-ресурс. Это не означает «установить предварительную (бета) версию приложения».

Согласовывайте значение в пределах одного типа ресурса

Здесь эта разница начинает иметь значение. allowPrerelease относится к модулю в целом, поэтому расхождение значения между записями с одинаковым resource: — обычно следствие того, что примеры собирали из разных источников. В примере выше он выставлен только у chrome, одной из двух записей Microsoft.WinGet.DSC/WinGetPackage, но причин различать его по приложениям нет, поэтому в пределах одного типа ресурса держите его одинаковым. Единственный критерий — опубликована ли стабильная версия модуля, предоставляющего ресурс: если стабильная версия есть, уберите его, и оставляйте только когда используете модуль, у которого пока есть лишь предварительные версии.

4.2. Разделение ролей между WinGet Configuration и собственным PowerShell

«Если WinGet Configuration идемпотентен, зачем в следующей главе писать собственный идемпотентный код?» — вопрос закономерный. Ответ в том, что покрываемые области разные; они не заменяют друг друга. Ниже разбор по требованиям.

Требование WinGet Configuration Собственный PowerShell (глава 5) Что выбрать на практике
Установка приложений, опубликованных в winget ◎ стандартный ресурс WinGetPackage ○ написать можно, но идемпотентность обеспечиваете сами Configuration
Настройки Windows (режим разработчика и подобное) ○ если есть соответствующий DSC-ресурс Configuration, если ресурс есть
Произвольные значения реестра (корпоративные стандартные настройки) △ зависит от доступных ресурсов ◎ можно записать что угодно PowerShell
Внутренние приложения в общей папке (MSI/EXE) △ вне области стандартных ресурсов PowerShell
Сетевые диски и общие принтеры × работа на уровне пользователя и при входе в систему PowerShell (фаза без повышения прав)
Различия по отделам и моделям машин △ разделять YAML ◎ достаточно подменить JSON конфигурации PowerShell
Целевая ОС старая (раньше 1809, winget раньше 1.6) × не соответствует требованиям PowerShell
Возврат средству развёртывания признака необходимости перезагрузки через код возврата △ трудно управлять ◎ решаете сами PowerShell

Держите канонический список приложений в одном месте

Вывод: то, что укладывается в область winget, идёт в Configuration, а что не укладывается — в PowerShell.

Но если вы используете оба, держите список приложений только в одном из них. Если записать пакеты в YAML Configuration и продублировать их же в wingetPackages файла kitting.config.json из главы 5, то при правке одного места второе останется устаревшим.

Если вы выбираете схему с совместным использованием Configuration, практично убрать установку приложений со стороны PowerShell и оставить в JSON только то, с чем Configuration не справляется, — значения реестра, общие принтеры и подобное. И наоборот: искать подходящие ресурсы, чтобы закрыть Configuration абсолютно всё, — работа, которая чаще всего не окупается.

5. Дополняем на PowerShell — то, чего winget не делает

Основную часть трудозатрат в реальной настройке занимает, как ни странно, всё, кроме установки приложений. Здесь и нужен PowerShell. Ключ в том, чтобы писать всё так, чтобы уже выполненное ничего не делало.

5.1. JSON конфигурации читают две фазы с разными правами

Глава длинная, поэтому сначала общая картина. Участвуют всего три файла: один JSON, определяющий, что устанавливать, который читают два скрипта, работающих с разными правами.

Фаза пользователя (без повышения прав, при каждом входе пользователя)Фаза администратора (с повышением прав, один раз на машину)перечитать разложенную конфигурациюInvoke-KsUserKitting.ps1Реестр HKCU /сетевые диски /общие принтерыInvoke-KsKitting.ps1Папки / реестр HKLM /компоненты Windows / пакеты winget /внутренние приложения (MSI, EXE)Раскладка JSON конфигурации в ProgramDatakitting.config.jsonопределение того, какой должна быть эта машина

Рис. 1: Трёхчастная структура настройки. Одну и ту же конфигурацию читают две фазы с разными правами

Принцип разделения один. Настройки, действующие на всю машину, идут в фазу администратора; настройки, создаваемые для каждого пользователя, — в фазу пользователя. Смешаете их — и, как описано ниже, обязательно получите аварию в виде «диска, который вроде бы подключён, не видно».

Что смотреть в скрипте администратора

Код ниже длинный, поэтому сначала что где написано. Скрипт администратора разделён комментариями --- N. ---, так что читайте только нужные части.

Раздел Что делает Кому читать
Начало (подготовка) Чтение JSON конфигурации, раскладка в C:\ProgramData, запуск журнала через Start-Transcript Всем
--- 1. Стандартные папки --- Создание папок. Самый простой пример идемпотентной записи Тем, кто хочет понять, как писать идемпотентный код
--- 2. Настройки реестра --- Запись в HKLM. Главное — сравнивать не только значение, но и тип Тем, кто распространяет корпоративные стандартные настройки
--- 3. Компоненты Windows --- Включение компонентов и то, как поймать необходимость перезагрузки Тем, кому нужен .NET Framework 3.5 и подобное
--- 4. Пакеты winget --- Определение того, установлен ли пакет, и запись без доверия к коду возврата Тем, кто хочет смотреть только часть про winget
--- 5. Внутренние приложения --- Тихий запуск MSI/EXE из общей папки. Сравнение версий и обработка кодов возврата Тем, кто распространяет внутренние приложения
Конец (обработка завершения) Выбор между возвратом 3010 / 1641 / 1 / 0 Тем, кто связывает это со средством развёртывания вроде Intune

5.2. JSON конфигурации решает, что будет настроено

Этот скрипт читает kitting.config.json. Полный пример файла конфигурации приложен как kitting.config.json к примерам кода этой статьи (архив в конце статьи), но структуру покажем сразу.

{
  "folders": [ "C:\\Work", "C:\\KsTools" ],
  "registry": [
    { "key": "HKLM:\\SOFTWARE\\Policies\\Microsoft\\Edge",
      "name": "HomepageLocation", "value": "https://intra.example.co.jp/", "type": "String" }
  ],
  "userRegistry": [
    { "key": "HKCU:\\Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Advanced",
      "name": "HideFileExt", "value": 0, "type": "DWord" }
  ],
  "windowsFeatures": [ "NetFx3" ],
  "wingetPackages": [ { "id": "Google.Chrome", "scope": "machine" } ],
  "internalApps": [
    { "displayName": "KsApp Клиент бизнес-системы",
      "installer": "\\\\fileserver\\deploy\\KsApp\\KsAppSetup.msi",
      "arguments": "/quiet /norestart INSTALLDIR=\"C:\\Program Files\\KsApp\"",
      "version": "3.2.0" }
  ],
  "drives":   [ { "letter": "S", "path": "\\\\fileserver\\share" } ],
  "printers": [ { "name": "\\\\printserver\\MFP-1F", "connection": "\\\\printserver\\MFP-1F" } ]
}

В таблице ниже — роль каждого ключа и то, какая фаза его читает. Весь замысел этой схемы виден именно в таблице.

Ключ Содержимое Читающая фаза Примечания
folders Пути стандартных папок, которые нужно создать Администратор (с повышением прав) Если они уже есть, ничего не делает
registry Корпоративные стандартные настройки, записываемые в HKLM Администратор (с повышением прав) Настройки политик, например домашняя страница Edge. Действуют на всех пользователей
userRegistry Настройки отдельного пользователя, записываемые в HKCU Пользователь (без повышения прав) Например, показ расширений файлов. Запись в HKLM не даёт эффекта
windowsFeatures Дополнительные компоненты Windows, которые нужно включить Администратор (с повышением прав) Может потребоваться перезагрузка
wingetPackages Значения id и scope пакетов, устанавливаемых через winget Администратор (с повышением прав) scope заранее проверьте командой winget show --scope (глава 2)
internalApps Внутренние приложения в общей папке (MSI/EXE) Администратор (с повышением прав) displayName должен точно совпадать с именем в «Программах и компонентах»
drives Подключения сетевых дисков Пользователь (без повышения прав) На уровне сеанса входа в систему. Созданные на стороне с повышением прав, они пользователю не видны
printers Подключения к общим принтерам Пользователь (без повышения прав) То же самое. Создаётся подключение для отдельного пользователя

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

Разделяйте настройки HKLM и HKCU

registry и userRegistry разделены потому, что они применяются в разных местах. Настройка вроде «показывать расширения файлов» (HideFileExt) в Проводнике читается из пользовательского HKCU, а не из политики HKLM. Запись в HKLM в фазе администратора отображение не меняет. Такие пользовательские настройки помещайте в userRegistry и применяйте в фазе без повышения прав, описанной ниже.

Для внутренних приложений указывайте отображаемое имя и требуемую версию

displayName в internalApps должен точно совпадать с именем, показанным в «Программах и компонентах». Если указать version, скрипт дополнительно проверит, установлена ли эта версия или более новая (если параметр опущен, решение принимается только по совпадению имени).

В этом примере JSON считается каноническим источником

Установку приложений как таковую можно выполнять и через winget import или WinGet Configuration из предыдущей главы, но этот скрипт включает и установку wingetPackages. Когда всё собрано в одном файле конфигурации, определение того, что должно стоять на машине, находится в одном месте, и одного запуска достаточно.

И наоборот: если оставить в файле конфигурации пункт, который скрипт не читает, вы получите запись о машине, где этого нет, как об «успешной настройке».

5.3. Настройте всю машину в фазе администратора

Здесь выполняются папки, HKLM, компоненты Windows, установка приложений и раскладка JSON конфигурации, который читает фаза пользователя. Общие диски и общие принтеры вынесены в следующую фазу.

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

#Requires -RunAsAdministrator
[CmdletBinding()]
param(
    [string] $ConfigPath = "$PSScriptRoot\kitting.config.json"
)

$ErrorActionPreference = 'Stop'
# Get-Content в PowerShell 5.1 читает в кодовой странице ANSI, если нет BOM.
# При чтении UTF-8 JSON в японской локали получается мусор, поэтому кодировку указываем явно
$config = Get-Content $ConfigPath -Raw -Encoding utf8 | ConvertFrom-Json
$rebootRequired  = $false
$rebootInitiated = $false
$log    = "C:\ProgramData\KsKitting\kitting_$(Get-Date -f yyyyMMdd_HHmmss).log"
$null   = New-Item -Path (Split-Path $log) -ItemType Directory -Force

# Фаза для отдельного пользователя, описанная ниже, работает в отдельном процессе,
# поэтому конфигурацию нужно разложить туда, откуда её прочитает любой пользователь.
# Если положить разложенные файлы сюда и запускать их отсюда же, источник и приёмник
# копирования окажутся одним и тем же файлом. Copy-Item считает копирование в себя ошибкой,
# и при $ErrorActionPreference = 'Stop' скрипт остановится ещё до начала настройки
$sharedConfig = 'C:\ProgramData\KsKitting\kitting.config.json'
$sourceFull   = (Resolve-Path $ConfigPath).ProviderPath
$sharedFull   = if (Test-Path $sharedConfig) { (Resolve-Path $sharedConfig).ProviderPath } else { '' }
if (-not [string]::Equals($sourceFull, $sharedFull, 'OrdinalIgnoreCase')) {
    Copy-Item $ConfigPath $sharedConfig -Force
}
Start-Transcript -Path $log -Append

try {
    # --- 1. Стандартные папки ---------------------------------------------
    foreach ($dir in $config.folders) {
        if (-not (Test-Path $dir)) {
            $null = New-Item -Path $dir -ItemType Directory
            Write-Verbose "Создано: $dir"
        }
    }

    # --- 2. Корпоративные настройки реестра -------------------------------
    foreach ($reg in $config.registry) {
        if (-not (Test-Path $reg.key)) { $null = New-Item -Path $reg.key -Force }
        $item   = Get-Item -Path $reg.key
        $exists = $item.GetValueNames() -contains $reg.name

        # Сравниваем не только значение, но и «тип». REG_SZ "1" и DWORD 1
        # в сравнении PowerShell оказываются равны, и настройка, которая
        # на самом деле не действует, ошибочно считается «уже применённой»
        $sameValue = $exists -and $item.GetValue($reg.name) -eq $reg.value
        $sameKind  = $exists -and $item.GetValueKind($reg.name).ToString() -eq $reg.type

        if (-not ($sameValue -and $sameKind)) {
            Set-ItemProperty -Path $reg.key -Name $reg.name -Value $reg.value -Type $reg.type
            Write-Verbose "Задано: $($reg.key)\$($reg.name) = $($reg.value) ($($reg.type))"
        }
    }

    # --- 3. Компоненты Windows ---------------------------------------------
    foreach ($feature in $config.windowsFeatures) {
        $state = Get-WindowsOptionalFeature -Online -FeatureName $feature
        if ($state.State -ne 'Enabled') {
            # Если перезагрузка подавлена через -NoRestart, её необходимость видна
            # в свойстве RestartNeeded возвращаемого значения. Если его не поймать,
            # скрипт завершится с 0, хотя перезагрузка требуется
            $result = Enable-WindowsOptionalFeature -Online -FeatureName $feature -NoRestart
            if ($result.RestartNeeded) { $rebootRequired = $true }
        }
    }

    # --- 4. Пакеты winget --------------------------------------------------
    # Код возврата winget может быть ненулевым, даже когда пакет уже установлен,
    # и наоборот, равным 0, когда его на самом деле нет. Об успехе судим по запросу состояния.
    # Без --scope он подхватит пользовательскую установку самого администратора
    # и ошибочно решит, что пакет «установлен в области machine»
    function Test-KsWingetPackage {
        param([string] $Id, [string] $Scope)
        $arguments = @('list', '--id', $Id, '--exact', '--accept-source-agreements')
        if ($Scope) { $arguments += @('--scope', $Scope) }
        $null = winget @arguments 2>&1
        return ($LASTEXITCODE -eq 0)
    }

    foreach ($package in $config.wingetPackages) {
        if (Test-KsWingetPackage -Id $package.id -Scope $package.scope) { continue }

        # Обратный апостроф переноса строки ставится в конце строки. Комментарий после него перенос отменяет
        winget install --id $package.id -e --silent `
            --accept-package-agreements --accept-source-agreements `
            --scope $package.scope
        $wingetExit = $LASTEXITCODE

        # Если здесь только выдать предупреждение и пойти дальше, средство развёртывания
        # запишет машину без обязательного приложения как «успешную настройку»
        if (-not (Test-KsWingetPackage -Id $package.id -Scope $package.scope)) {
            throw "Не удалось установить $($package.id) (winget ExitCode=$wingetExit)"
        }
    }

    # --- 5. Внутренние приложения (тихий запуск установщика из общей папки) ----
    # Список установленных приложений открываем с явным указанием обоих представлений, 32- и 64-битного.
    # Когда 32-битный PowerShell работает в 64-битной Windows (при некоторых конфигурациях Intune),
    # перенаправление WOW64 уводит HKLM:\SOFTWARE\... в 32-битное представление, и 64-битное
    # приложение ошибочно считается «неустановленным» и переустанавливается каждый раз
    $installedApps = foreach ($view in 'Registry64', 'Registry32') {
        $baseKey = [Microsoft.Win32.RegistryKey]::OpenBaseKey('LocalMachine', $view)
        try {
            $uninstall = $baseKey.OpenSubKey('SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall')
            if (-not $uninstall) { continue }
            try {
                foreach ($name in $uninstall.GetSubKeyNames()) {
                    $appKey = $uninstall.OpenSubKey($name)
                    if (-not $appKey) { continue }
                    try {
                        $displayName = $appKey.GetValue('DisplayName')
                        if ($displayName) {
                            [pscustomobject]@{
                                DisplayName    = [string] $displayName
                                DisplayVersion = [string] $appKey.GetValue('DisplayVersion')
                            }
                        }
                    }
                    finally { $appKey.Dispose() }
                }
            }
            finally { $uninstall.Dispose() }
        }
        finally { $baseKey.Dispose() }
    }

    foreach ($app in $config.internalApps) {
        $installed = $installedApps | Where-Object DisplayName -eq $app.displayName

        # Не решаем «уже установлено» по одному совпадению DisplayName. На машине,
        # где осталась старая версия, новая так и не появится, и повторный запуск
        # никогда не сойдётся к состоянию из файла конфигурации. Если в конфигурации
        # есть version, проверяем, установлена ли эта версия или более новая
        $upToDate = if ($app.version) {
            $wanted = [version] $app.version
            [bool]($installed | Where-Object {
                $cur = $_.DisplayVersion -as [version]   # обозначения не в формате версии не учитываются
                $cur -and $cur -ge $wanted
            })
        } else {
            [bool] $installed
        }
        if ($upToDate) { continue }

        # -ArgumentList у Start-Process просто соединяет массив пробелами в одну командную строку,
        # поэтому значения с пробелами нужно заключать в кавычки на стороне файла конфигурации
        # Пример: '/quiet /norestart INSTALLDIR="C:\Program Files\KsApp"'
        # .msi не является исполняемым файлом, поэтому прямая передача в Start-Process
        # завершается ошибкой «не является приложением». Запускаем через msiexec
        if ([System.IO.Path]::GetExtension($app.installer) -eq '.msi') {
            $msiArgs = @('/i', "`"$($app.installer)`"") + $app.arguments
            $proc = Start-Process -FilePath 'msiexec.exe' -ArgumentList $msiArgs -Wait -PassThru -NoNewWindow
        }
        else {
            $proc = Start-Process -FilePath $app.installer -ArgumentList $app.arguments -Wait -PassThru -NoNewWindow
        }
        # У Windows Installer успех — это не только 0.
        # 3010 и 1641 тоже успех, различается лишь обработка перезагрузки
        switch ($proc.ExitCode) {
            0    { }                                   # успех
            3010 { $rebootRequired = $true }           # успех. Требуется перезагрузка (ERROR_SUCCESS_REBOOT_REQUIRED)
            1641 { $rebootInitiated = $true }          # успех. Установщик начал перезагрузку
            default {
                throw "Не удалось установить $($app.displayName) (ExitCode=$($proc.ExitCode))"
            }
        }
        # Если перезагрузка уже началась, последующие установки всё равно будут прерваны
        if ($rebootInitiated) { break }
    }

    if ($rebootInitiated) {
        # 1641 означает «успех, но перезагрузка уже начата». Если вернуть 0, средство
        # развёртывания решит, что «работа завершилась и система сама перезагрузилась»,
        # поэтому код передаётся как есть
        Write-Host 'Установщик начал перезагрузку. Повторно запустите это после перезагрузки' -ForegroundColor Yellow
        exit 1641
    }

    if ($rebootRequired) {
        # Если вернуть 3010 как есть, Intune или средство развёртывания прочитает это как
        # «успех, требуется перезагрузка» и сможет запланировать и отразить её. Возврат 0 здесь
        # означает, что про перезагрузку забудут
        Write-Host 'Настройка завершена (требуется перезагрузка)' -ForegroundColor Yellow
        exit 3010
    }

    Write-Host 'Настройка завершена' -ForegroundColor Green
    exit 0
}
catch {
    Write-Warning "Сбой: $($_.Exception.Message)"
    Write-Warning $_.InvocationInfo.PositionMessage
    exit 1
}
finally {
    Stop-Transcript
}

Как читать коды возврата скрипта администратора

Возвращаемый код Что он означает в этом скрипте Что делать дальше
0 Работа завершена, перезагрузка не запрашивается Записать как завершённую
3010 Успех, но для вступления изменений в силу нужна перезагрузка Сообщить средству развёртывания о необходимости перезагрузки
1641 Успех. Установщик начал перезагрузку Не продолжать, а повторить запуск после перезагрузки
1 Скрипт перехватил ошибку Посмотреть в журнале, где произошёл сбой

3010 и 1641 у Windows Installer — не сбой. И наоборот: возвращая вызывающей стороне только 0, вы лишаетесь возможности сообщить, что перезагрузка нужна или уже началась.6

5.4. Фазу пользователя запускайте без повышения прав, при входе пользователя

Обратите внимание, что подключение сетевых дисков намеренно исключено из этого скрипта администратора. Подключение буквы диска — настройка уровня сеанса входа, поэтому созданное в сеансе с повышением прав до администратора оно не видно из обычного Проводника пользователя при UAC.

Если запустить это с системными правами, например из Intune, подключение вообще попадёт в сеанс SYSTEM и пользователю не поможет. Правильный ответ — применять пользовательские настройки без повышения прав, при входе этого пользователя в систему.

# Пользовательские настройки (запускать без повышения прав, при входе пользователя. Как зарегистрировать — описано ниже)

# Это отдельный процесс, отличный от скрипта администратора, поэтому $config не наследуется.
# Перечитываем конфигурацию, которую фаза администратора разложила в ProgramData
$configPath = 'C:\ProgramData\KsKitting\kitting.config.json'
if (-not (Test-Path $configPath)) {
    Write-Warning "Файл конфигурации не найден: $configPath"
    exit 1
}
$config = Get-Content $configPath -Raw -Encoding utf8 | ConvertFrom-Json

# Пользовательские настройки реестра (HideFileExt и подобные. Запись в HKLM не даёт эффекта)
foreach ($reg in $config.userRegistry) {
    if (-not (Test-Path $reg.key)) { $null = New-Item -Path $reg.key -Force }

    $item   = Get-Item -Path $reg.key
    $exists = $item.GetValueNames() -contains $reg.name
    $same   = $exists -and
              $item.GetValue($reg.name) -eq $reg.value -and
              $item.GetValueKind($reg.name).ToString() -eq $reg.type

    if (-not $same) {
        Set-ItemProperty -Path $reg.key -Name $reg.name -Value $reg.value -Type $reg.type
    }
}

# Сетевые диски
foreach ($drive in $config.drives) {
    $local    = "$($drive.letter):"
    $existing = Get-SmbMapping -LocalPath $local -ErrorAction SilentlyContinue
    if ($existing) {
        # Решение только по «есть ли подключение» приводит к тому, что при переезде
        # общей папки и изменении настройки старое подключение остаётся, а результат
        # всё равно сообщается как «успех». Сравниваем и назначение
        if ($existing.RemotePath.TrimEnd('\') -eq $drive.path.TrimEnd('\')) { continue }
        Remove-SmbMapping -LocalPath $local -Force
    }
    New-SmbMapping -LocalPath $local -RemotePath $drive.path -Persistent $true
}

# Подключения к общим принтерам тоже относятся к пользователю. При запуске от администратора
# или от SYSTEM подключение создаётся только для этой учётной записи и пользователю не видно
foreach ($printer in $config.printers) {
    if (-not (Get-Printer -Name $printer.name -ErrorAction SilentlyContinue)) {
        Add-Printer -ConnectionName $printer.connection
    }
}

Принтеры, HKCU и профиль тоже относятся к стороне пользователя

Подключение к общему принтеру (Add-Printer -ConnectionName) обрабатывается так же. Это операция, создающая подключение для отдельного пользователя, поэтому выполненная в скрипте администратора или при запуске Intune от SYSTEM она не будет видна сотруднику, который войдёт позже. Если нужно, чтобы принтер был на каждой машине, используйте механизм развёртывания принтеров на уровне машины, например распространение политики с сервера печати, либо выполняйте это в фазе без повышения прав.

По той же причине размещение файлов в профиле пользователя, запись в HKCU и создание ярлыков для пользователя тоже входят в эту фазу без повышения прав. Двухступенчатая схема «настройки всей машины — один раз от администратора, настройки пользователя — без повышения прав при каждом входе» и есть базовая форма скрипта настройки. Об осторожностях с UNC-путями и подключением дисков см. также «Подводные камни сетевых общих папок и UNC-путей».

5.5. Выберите один способ запуска при входе в систему

Суть двухступенчатой схемы — механизм, который запускает этот скрипт без повышения прав при входе пользователя, с правами самого этого пользователя. Если это не связано, отработает только фаза администратора, и получится машина, на которой не настроены ни диски, ни принтеры. Способов три.

Способ Когда запускается Для каких окружений подходит Осторожности
Ключ Run в HKLM При каждом входе любого пользователя Без членства в домене. Хочется закрыть всё одним скриптом из Intune или средства развёртывания Запускается каждый раз, поэтому идемпотентность обязательна. Нужно указание, запрещающее показ окна
Планировщик заданий (триггер при входе) При каждом входе любого пользователя Хочется сохранять историю результатов запуска. Хочется отложенный запуск Снимите флажок «Выполнять с наивысшими правами» (если его поставить, задача получит повышение прав и смысл потеряется)
Сценарий входа (групповая политика) При каждом входе любого пользователя С членством в домене Вписывается в существующую эксплуатацию GPO, но для отдельной машины неудобно

Способ 1: зарегистрировать в ключе Run в HKLM

Регистрация выполняется с правами администратора. Если встраиваете это в скрипт администратора, поставьте код перед exit, который возвращает код завершения. JSON конфигурации, который читает фаза пользователя, раскладывается в фазе администратора из раздела 5.3.

# Размещаем скрипт фазы пользователя там, откуда его прочитает любой пользователь
$userScript = 'C:\ProgramData\KsKitting\Invoke-KsUserKitting.ps1'
Copy-Item "$PSScriptRoot\Invoke-KsUserKitting.ps1" $userScript -Force

# Ключ Run в HKLM выполняется с правами вошедшего пользователя (без повышения прав).
# Попытка писать вместо этого в HKCU приведёт к тому, что, поскольку всё работает
# от администратора или от SYSTEM, запись уйдёт в «HKCU самого администратора»,
# а не в HKCU сотрудника, который будет пользоваться машиной
$runKey  = 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Run'
$command = 'powershell.exe -NoProfile -ExecutionPolicy Bypass ' +
           "-WindowStyle Hidden -File `"$userScript`""
Set-ItemProperty -Path $runKey -Name 'KsUserKitting' -Value $command

Способ 2: зарегистрировать задание в Планировщике заданий (когда нужна история)

На случай, если вы пропустили способ 1 и начали читать отсюда, повторяем размещение скрипта с самого начала.

# Размещаем там, откуда прочитает любой пользователь (как в способе 1)
$userScript = 'C:\ProgramData\KsKitting\Invoke-KsUserKitting.ps1'
New-Item -ItemType Directory -Path (Split-Path $userScript) -Force | Out-Null
Copy-Item -Path '.\Invoke-KsUserKitting.ps1' -Destination $userScript -Force

$action = New-ScheduledTaskAction -Execute 'powershell.exe' `
    -Argument "-NoProfile -ExecutionPolicy Bypass -WindowStyle Hidden -File `"$userScript`""

$trigger = New-ScheduledTaskTrigger -AtLogOn

# Указание BUILTIN\Users (S-1-5-32-545) приводит к запуску с правами того, кто вошёл.
# RunLevel Limited — это указание «не повышать права». Если поставить здесь Highest,
# задача пойдёт в сеансе с повышением прав, и подключения дисков станут не видны пользователю
$principal = New-ScheduledTaskPrincipal -GroupId 'S-1-5-32-545' -RunLevel Limited

Register-ScheduledTask -TaskName 'KsUserKitting' `
    -Action $action -Trigger $trigger -Principal $principal -Force

Способ 3: сценарий входа групповой политики

Настройка находится в Конфигурация пользователя > Политики > Конфигурация Windows > Сценарии (вход/выход) > Вход в системуgpedit.msc на отдельной машине уровня Политики нет, поэтому путь — Конфигурация пользователя > Конфигурация Windows > Сценарии (вход/выход)). Добавьте сценарий на вкладке «Сценарии PowerShell» диалогового окна. Если записать .ps1 прямо на вкладке «Сценарии», он будет обработан как исполняемый файл и поведёт себя не так, как задумано. В случае локальной политики сам файл сценария размещается в %SystemRoot%\System32\GroupPolicy\User\Scripts\Logon.

Раз запускается каждый раз — проверяйте текущее состояние

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

5.6. Проверьте повторные запуски, след и связку со средством развёртывания

Проверьте описанную схему, прежде чем переводить её в эксплуатацию.

Согласуйте права, настройки и текущее состояние

  • Разделите настройки, требующие повышения прав, и пользовательские настройки. Как сказано выше, их смешение приводит к аварии вида «диска, который вроде бы подключён, не видно»
  • Вынесите настройки в JSON. Тогда различия по отделам и моделям машин можно выразить, не меняя скрипт
  • Проверяйте текущее состояние перед каждой операцией. Именно это делает запуск безопасным при любом числе повторов

Сохраняйте журнал того, что было сделано

Сохраняйте след с помощью Start-Transcript. После этого можно проследить, что было сделано на конкретной машине («Потоки вывода PowerShell и проектирование журналов»)

Сообщайте о необходимости перезагрузки тоже кодом возврата

Возвращайте код завершения. Тогда Intune или средство развёртывания сможет определить успех или сбой. Учтите, что у Windows Installer успех — это не только 0. И 3010 (ERROR_SUCCESS_REBOOT_REQUIRED, требуется перезагрузка), и 1641 (ERROR_SUCCESS_REBOOT_INITIATED, перезагрузка уже начата) — успех.6 Сведите их в default и считайте сбоем — и успешная установка будет отмечена в средстве развёртывания красным. Считайте их успехом, а ключ к этому — вернуть тот же код вызывающей стороне в конце. Вернёте 0 — и средство развёртывания лишится способа узнать о необходимости перезагрузки («Обработка ошибок и проектирование повторов в PowerShell»)

Для аргументов с пробелами проверяйте кавычки

Следите за кавычками в аргументах установщика. Start-Process -ArgumentList лишь соединяет массив пробелами, поэтому границы аргументов не сохраняются. Либо заключайте пути с пробелами в кавычки на стороне файла конфигурации, либо используйте ProcessStartInfo.ArgumentList (PowerShell 7) («Как правильно вызывать внешние exe из PowerShell»)

6. Использование winget из модуля PowerShell

Работайте с объектами, а не со строками

Использовать модуль PowerShell надёжнее, чем разбирать вывод командной строки как строки. Microsoft.WinGet.Client предоставляет командлеты вроде Find-WinGetPackage / Install-WinGetPackage / Get-WinGetPackage / Update-WinGetPackage.4

Install-Module -Name Microsoft.WinGet.Client -Scope AllUsers -Force

# Проверяем, установлено ли, и только потом ставим (идемпотентность)
foreach ($id in 'Google.Chrome', 'Microsoft.VisualStudioCode', '7zip.7zip') {
    if (Get-WinGetPackage -Id $id -ErrorAction SilentlyContinue) {
        Write-Verbose "Уже установлено: $id"
        continue
    }
    Install-WinGetPackage -Id $id -Mode Silent -Scope System
}

Результаты возвращаются как объекты, поэтому проверку успеха и сверку списков можно писать напрямую — в этом преимущество.

7. Осторожности при запуске без оператора и в системном контексте

Любая попытка полностью автоматизировать настройку упирается в вопрос, под какой учётной записью это выполняется. Даже после проверки областей из главы 2 и разделения фаз из главы 5 последнее, что нужно сделать, — проверить работу под той же учётной записью, что используется при развёртывании.

Не исходите из того, что это работает под SYSTEM

  • Часть возможностей winget рассчитана на выполнение в контексте пользователя. Выполнение в системном контексте Microsoft пока указывает как будущую возможность3

    Не путайте то, что работало от администратора, с окружением пользователя

  • Разделяйте работу, требующую повышения прав, и работу отдельного пользователя. Схема, в которой настройки всей машины применяются с правами администратора, а настройки в профиле пользователя — при первом входе в систему, удобнее в работе

    Проверяйте в тех же условиях, что и при развёртывании

  • Всегда проверяйте под реальной учётной записью запуска. «У меня под администратором работало, а после развёртывания не работает» — самая частая неудача в этой области («Когда задачи Планировщика заданий не выполняются»)

8. Практические правила (таблица решений)

В завершение сопоставьте каждый пункт регламента с этой таблицей. Решите не только средство выполнения, но и права, повторные запуски, запись и перезагрузку — и настройку можно будет выстроить как эксплуатационный процесс.

Что делать Средство Примечания
Установка коммерческих и открытых продуктов winget install / configure --silent --accept-* обязательны1
Выгрузка конфигурации существующей эталонной машины winget export Настройки не входят. Используйте как отправную точку2
Приложения + настройки Windows декларативно winget configure (YAML) Windows 10 1809 или более поздняя + winget 1.6 или более поздний3
Реестр, компоненты Windows, внутренние приложения PowerShell (администратор) Проверьте состояние перед изменением (идемпотентность)
Общие диски, общие принтеры, пользовательские настройки PowerShell (при входе, без повышения прав) Созданные с повышением прав или от SYSTEM, они не видны пользователю
Внутренние приложения PowerShell + тихий установщик Строить отдельный репозиторий при малом масштабе избыточно
Управление из PowerShell Microsoft.WinGet.Client Избавляет от разбора вывода как строк4
Запуск без оператора Проверка под учётной записью запуска У системного контекста есть ограничения3
Запись о выполненном Start-Transcript + код возврата Сохраняет, что было сделано на каждой машине
Когда нужна перезагрузка Возврат кода 3010 / 1641 Оба — успех. Вернёте 0, и средство развёртывания не распознает перезагрузку6

9. Итоги

  • Регламент настройки можно заменить исполняемыми файлами. Реалистичное разделение — установку приложений оставить winget, а всё остальное — PowerShell.
  • В winget install обязательно добавляйте --silent и --accept-package-agreements --accept-source-agreements. Забудете их — и запуск без оператора остановится.
  • winget export позволяет выгрузить конфигурацию с существующей эталонной машины, но настройки и приложения вне управления winget в неё не входят.
  • Используйте WinGet Configuration (winget configure) — и приложения вместе с настройками соберутся в один декларативный файл, а схема станет устойчивой к повторным запускам.
  • Сторону PowerShell всегда пишите так, чтобы текущее состояние проверялось перед изменением, — это обеспечивает идемпотентность.
  • Разделяйте фазы выполнения: настройки всей машины — с правами администратора, пользовательские настройки вроде сетевых дисков и общих принтеров — без повышения прав при входе в систему. Подключения, созданные в сеансе с повышением прав или от SYSTEM, пользователю не видны.
  • При запуске без оператора важнее всего проверка учётной записи запуска. Проектируйте исходя из того, что у запуска winget в системном контексте есть ограничения.

Загрузка примеров кода

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

Скачать примеры кода (zip)

Примеры из этой статьи зависят от Windows и от тенанта, поэтому их работа не проверялась запуском. Синтаксический разбор и статический анализ с помощью PSScriptAnalyzer выполнены для всех файлов, но поведение обязательно проверьте на своей тестовой машине.

# Синтаксический разбор + статический анализ (работает и не в Windows)
./Invoke-SampleTests.ps1

Настроенные значения (пути, имена серверов, идентификаторы тенантов и так далее) приведены как пример. Не запускайте их как есть в рабочей среде — переделайте под своё окружение.

Похожие статьи

Смежные области консультаций

ООО «КомураСофт» занимается настройкой ПК, автоматизацией корпоративной стандартной среды, превращением в исполняемые процессы, которые держатся на регламенте и на одном человеке, и поддержкой проектирования скриптов развёртывания.

Справочные материалы

  1. Microsoft Learn, команда install (winget). О выборе цели через –id / -e, установке без участия оператора с –silent, согласии с условиями использования через –accept-package-agreements / –accept-source-agreements и указании области установки (user / machine) через –scope. См. также список команд в разделе Use WinGet to install and manage applications 2 3

  2. Microsoft Learn, команда export (winget). О том, что список установленных пакетов можно записать в JSON, о записи версий с –include-versions, о восстановлении через команду import и поведении –ignore-unavailable, об ограничении экспорта пакетами под управлением winget и об иерархии выходного JSON (Sources / Packages / PackageIdentifier / Version, где Version необязателен). Структура JSON также определена в packages.schema.2.0.json: на верхнем уровне WinGetVersion, CreationDate и Sources, каждый элемент Sources содержит SourceDetails (Name / Identifier / Argument / Type) и Packages, а каждый элемент Packages требует PackageIdentifier и может содержать Version и другие поля.  2 3 4 5

  3. Microsoft Learn, WinGet Configuration. О том, что WinGet Configuration — механизм, который объявляет желаемое состояние в YAML и применяет его с помощью PowerShell DSC, что его можно использовать для установки без участия оператора, что требуются Windows 10 версии 1809 (сборка 17763) или более поздняя либо Windows 11 и WinGet v1.6.2631 или более поздний, как обрабатывается UAC при запуске из консоли администратора и что выполнение в системном контексте указано как пункт будущей разработки. См. также show и –accept-configuration-agreements в команде configure 2 3 4 5 6 7

  4. GitHub, microsoft/winget-cli — модуль PowerShell Microsoft.WinGet.Client. О том, что модуль Microsoft.WinGet.Client можно установить из PowerShell Gallery, и о предоставляемых им командлетах Find-WinGetPackage / Get-WinGetPackage / Install-WinGetPackage / Update-WinGetPackage / Uninstall-WinGetPackage, позволяющих работать с результатами как с объектами.  2 3

  5. Microsoft Learn, команда show (winget). О том, что это команда, показывающая сведения об указанном приложении (метаданные и данные об установщике), о возможности выбрать область установки (user / machine) опцией –scope и о том, что отображаемые сведения об установщике основаны на указанных аргументах и на собственном определении WinGet. 

  6. Microsoft Learn, Коды ошибок Windows Installer. О том, что ERROR_SUCCESS_REBOOT_REQUIRED (3010) означает «для вступления изменений в силу требуется перезагрузка; сама установка завершилась успешно», а ERROR_SUCCESS_REBOOT_INITIATED (1641) — «установщик начал перезагрузку», то есть код, означающий успех.  2 3

Недавние статьи с теми же тегами помогут подробнее изучить близкие темы.

Политика аудита безопасности Windows и расследование журнала событий на практике — как стать ИТ-службой, которая умеет читать 4625

Практическое руководство, чтобы ответить на просьбу «посмотрите журналы неудачных входов». Разбирает связь базовой и расширенной политики...

Эти страницы показывают тему статьи в более широком контексте услуг и решений.

Статья напрямую связана со следующими услугами.

Частые вопросы

Вопросы, которые часто возникают при консультациях по теме статьи.

Можно ли с помощью winget import автоматизировать настройку ПК целиком?
Установку приложений — можно, но этого недостаточно. winget import воспроизводит только список пакетов; настройки внутри приложений, добавление принтеров, подключение сетевых дисков, параметры электропитания, корпоративные стандартные настройки через реестр и так далее в него не входят. Приложения, установленные не через winget, и собственные внутренние бизнес-приложения тоже не попадают в экспорт. На практике реалистичен двухэтапный подход: установку приложений оставить winget, а остальные настройки дописать скриптом PowerShell.
Чем winget отличается от WinGet Configuration (winget configure)?
winget install/import — это процедурные инструкции вида «установи это в таком порядке», а WinGet Configuration — способ записать в YAML-файл декларацию «в итоге система должна быть в таком состоянии». Внутри используется PowerShell DSC, поэтому одним файлом можно выразить не только установку приложений, но и настройки Windows и конфигурацию приложений. Если желаемое состояние уже достигнуто, ничего не выполняется, поэтому при сбое на середине достаточно повторно запустить тот же файл — это преимущество, когда настройку приходится переделывать. Требуются Windows 10 1809 или более поздняя версия и winget 1.6 или более поздний.
Безопасно ли запускать winget с правами SYSTEM из Планировщика заданий или Intune?
Нужна осторожность. Часть возможностей winget рассчитана на выполнение в контексте пользователя, и выполнение в системном контексте Microsoft пока указывает как пункт будущей разработки. На практике это обходят так: устанавливают для всех пользователей с --scope machine, запускают через модуль PowerShell (Microsoft.WinGet.Client) или выполняют в контексте пользователя при первом входе в систему. В любом случае обязательно проверяйте работу под той учётной записью, от имени которой скрипт будет реально запускаться.
Должен ли скрипт настройки быть безопасным при любом числе запусков?
Да. Идемпотентность (одинаковый результат при любом числе запусков) считайте обязательным условием. Настройка регулярно срывается на середине, и каждый раз должна быть возможность начать заново. Если написать так, чтобы создание папок сначала проверяло наличие через Test-Path, настройка реестра сначала сверяла текущее значение, а установка приложений — наличие приложения, работу можно продолжить с места сбоя. В WinGet Configuration такой подход заложен изначально.
Можно ли распространять собственные внутренние бизнес-приложения через winget?
Это возможно, если поднять внутренний приватный репозиторий (источник с REST API), но для него придётся создать и сопровождать сервер. Если приложений всего несколько, проще тихо запустить установщик из общей папки скриптом PowerShell. Для небольших организаций самый реалистичный вариант разделения такой: коммерческие и открытые продукты оставить winget, а внутренние приложения ставить через PowerShell.

Об авторе

Страница с профилем автора статьи.

Го Комура

Представитель KomuraSoft LLC

Специализируется на разработке программного обеспечения для Windows, техническом консалтинге и расследовании сбоев, особенно в проектах с унаследованными системами и трудно воспроизводимыми ошибками.

Публичные ссылки

Вернуться в блог