DSC интересен тем, кто придерживается DevOps-подхода. Этот инструмент хорошо укладывается в парадигму Infrastructure as a Code: разработчики могут вносить свои требования в конфигурацию и включать ее в систему управления версиями, а команды — развертывать код без использования «ручных» процессов.
Совместно со Станиславом Булдаковым из Райффайзенбанка мы объединили наш опыт работы с движком DSC и разделили его на 2 статьи. В первой мы разберем основные принципы работы и познакомимся с особенностями использования на практических примерах:
- «распакуем коробку» с движком DSC, посмотрим, какие ресурсы есть по умолчанию, и покажем, где взять дополнительные ресурсы;
- разберем, как описывать конфигурацию в DSC;
- узнаем, как встроенный агент Local Configuration Manager применяет конфигурации на сервере, покажем, как он настраивается с помощью метаконфигураций;
- перейдем к более сложным случаям настройки: частичным конфигурациям и конфигурациям-заглушкам.
А про настройку и особенности работы в режимах Push и Pull можно узнать во второй статье.
DSC — легковесный и быстрый движок. Например, c его помощью можно намного быстрее установить .NET Framework 3.5 на виртуальные машины. Вот что помогает ему ускорять процесс настройки сервисов:
- Доступность «из коробки» на всех Windows с предустановленным PowerShell.
DSC входит в привычный нам PowerShell и базируется на штатных механизмах Windows Management Framework. С Linux чуть сложнее. Как минимум, нужно будет доустановить сам PowerShell. - Декларативный, а не императивный способ управления целевыми хостами.
Мы описываем, какие настройки должны быть выполнены, но не описываем, как это должно быть сделано. Движок самостоятельно разберется с этим с помощью ресурсов DSC. - Идемпотентность, то есть способность выдавать один и тот же результат при многократных вызовах.
DSC удобно использовать для аудита конфигурации и приведения настроек к тем, которые заранее определены для сервера. Эта особенность гарантирует неизменность настроек и возможность быстро откатиться к ним.
Конфигурации всегда выполняются последовательно, без условий и ветвлений. Поэтому кратко алгоритм работы DSC выглядит так:
- Конфигурируем Local Configuration Manager (LCM). Это встроенный агент, который отвечает за применение конфигураций на сервере. Мы передаем ему, как заявленные конфигурации должны работать и в каком порядке.
- Если необходимы дополнительные ресурсы, заранее их устанавливаем и подключаем.
- В декларативной форме пишем порядок конфигурации.
- Транслируем конфигурацию в формат MOF-файла.
- Отправляем конфигурацию на целевой свежеразвернутый или уже действующий сервер.
- LCM получает конфигурацию (MOF-файл) плюс инструкции по настройке самого LCM.
- Конфигурация тут же применяется по нашей команде.
Упрощенная схема архитектуры DSC.
Знакомство с движком начнем с изучения предустановленных ресурсов. Далее попробуем написать конфигурации.
Ресурсы DSC
Ресурсы DSC — своеобразный аналог модулей PowerShell. Любой сервер Windows уже имеет предустановленный набор ресурсов DSC, они лежат в том же каталоге, что и модули PowerShell. Список можно получить через командлет Get-DscResourse. Вот как этот список выглядит на Windows 10 1809:
PS C:\windows\system32> Get-DscResource | Sort-Object -Property Name | ft ImplementedAs, Name -a
ImplementedAs Name
------------- ----
PowerShell Archive
PowerShell Environment
Binary File
PowerShell Group
Composite GroupSet
Binary Log
PowerShell Package
PowerShell PackageManagement
PowerShell PackageManagementSource
Composite ProcessSet
PowerShell Registry
PowerShell Script
PowerShell Service
Composite ServiceSet
Binary SignatureValidation
PowerShell User
PowerShell WaitForAll
PowerShell WaitForAny
PowerShell WaitForSome
PowerShell WindowsFeature
Composite WindowsFeatureSet
PowerShell WindowsOptionalFeature
Composite WindowsOptionalFeatureSet
PowerShell WindowsPackageCab
PowerShell WindowsProcess
Названия ресурсов дают понимание, с чем они работают:
- Archive — с упаковкой и распаковкой архивов;
- Environment — с переменными окружения;
- File — с файлами;
- Group — с локальными группами пользователей;
- Log — с логами;
- Package —- с пакетами ПО;
- Registry —- с ключами реестра и их состоянием;
- Service — с сервисами и их состоянием;
- User — с локальными учетными записями пользователей;
- WindowsFeature — с компонентами Windows Server;
- WindowsProcess — с процессами Windows;
- Script — вставляет полноценный PowerShell-скрипт в тело конфигурации. Имеет 3 отдельных блока: SetScript — сам скрипт-блок, TestScript — условие, при нарушении которого будет запущен скрипт, GetScript — возвращает текущее состояние объекта.
Чаще всего ресурсов DSC «из коробки» недостаточно. В этом случае можно написать свои скрипты для всего, что выходит за пределы штатного модуля. Чтобы не изобретать велосипед, можно использовать ресурсы DSC, написанные другими разработчиками. Например, отсюда https://github.com/PowerShell/DscResources или из PSGallery.
Как установить дополнительные ресурсы. Используем командлет Install-Module. Установка ресурсов — это обычное копирование файлов ресурса по одному из путей в переменной окружения $env:PSModulePath. Установленные ресурсы автоматически не подключаются по ходу компиляции, поэтому позже мы дополнительно подключим их в самой конфигурации.
Для использования ресурса нужно установить его локально и на целевом сервере. В On-Premise инфраструктурах политика безопасности обычно запрещает доступ в интернет для серверов. В этом случае сервер DSC не сможет подгрузить дополнительные ресурсы из внешних источников. Для публикации архивов с модулями мы разворачиваем локальный репозиторий NuGet или обычный веб-сервер. Для веб-сервера установить дополнительные ресурсы можно с помощью распаковки модуля в каталог C:\Program Files\WindowsPowerShell\Modules\
Это как раз и делает командлет Install-Module.
Во второй статье мы подробнее рассмотрим, чем отличается установка для режимов Push и Pull.
Разбор простой конфигурации
Конфигурация — это простое последовательное описание того, что должно быть сделано на сервере. Так выглядит простая конфигурация DSC:
Configuration EnvironmentVariable_Path
{
param ()
Import-DscResource -ModuleName 'PSDscResources'
Node localhost
{
Environment CreatePathEnvironmentVariable
{
Name = 'TestPathEnvironmentVariable'
Value = 'TestValue'
Ensure = 'Present'
Path = $true
Target = @('Process', 'Machine')
}
}
}
EnvironmentVariable_Path -OutputPath:"C:\EnvironmentVariable_Path"
На ее примере посмотрим, из чего состоит конфигурация.
Блок Configuration — специальный тип функции PowerShell с описанием того, что мы хотим получить.
Внутри блока содержится:
- блок param с параметрами, которые могут использоваться внутри;
- блок с дополнительными вызовами PowerShell. Здесь в начале конфигурации мы всегда запускаем Import-Resource, чтобы подключить дополнительные ресурсы;
- блоки с настройками для конкретных серверов Node $servername.
Внутри блока Node мы указываем, какие ресурсы на конкретном сервере мы будем настраивать. В примере выше это создание переменной окружения с использованием штатного ресурса Environment.
Заглянем чуть глубже и посмотрим синтаксис конкретного ресурса через команду Get-DscResource -Name Environment -Syntax:
PS C:\windows\system32> Get-DscResource -Name Environment -Syntax
Environment [String] #ResourceName
{
Name = [string]
[DependsOn = [string[]]]
[Ensure = [string]{ Absent | Present }]
[Path = [bool]]
[PsDscRunAsCredential = [PSCredential]]
[Value = [string]]
}
В этом примере:
- Name — имя переменной окружения.
- В DependsOn указываем зависимость от других ресурсов. Важно помнить, что операция не будет выполнена, пока не будет выполнен указанный здесь ресурс.
- В Ensure мы указываем условия выполнения конфигурации. Если такая переменная отсутствует, то мы ее создадим; если она будет присутствовать, то этот ресурс настраиваться не будет.
- В Path указывается, будет ли переменная окружения содержать пути.
- В PsDscRunAsCredential указываются учетные данные.
- Value — значение переменной окружения.
- В Target может указываться, в отношении кого будет применена конфигурация.
Как запустить компиляцию. Просто вызываем конфигурацию по ее имени с необходимыми параметрами. Результатом станет mof-файл, который в дальнейшем используется движком DSC для настройки конкретного сервера:
PS C:\windows\system32> EnvironmentVariable_Path -OutputPath:"C:\EnvironmentVariable_Path"
Directory: C:\EnvironmentVariable_Path
Mode LastWriteTime Length Name
---- ------------- ------ ----
-a---- 25.02.2020 14:05 2172 localhost.mof
Настройка Local Configuration Manager
Local configuration manager отвечает за применение конфигураций, которые мы скомпилировали в mof-файлы. Именно он следит за сохранением состояния, определенного в конфигурации. Если система выходит из этого состояния, LCM вызывает код в ресурсах для восстановления заданного состояния.
Посмотрим настройки Local configuration manager через Get-DscLocalConfigurationManager:
PS C:\windows\system32> Get-DscLocalConfigurationManager
ActionAfterReboot : ContinueConfiguration
AgentId : 1FB3A2EE-57C9-11EA-A204-58A023EF3A48
AllowModuleOverWrite : False
CertificateID :
ConfigurationDownloadManagers : {}
ConfigurationID :
ConfigurationMode : ApplyAndMonitor
ConfigurationModeFrequencyMins : 15
Credential :
DebugMode : {NONE}
DownloadManagerCustomData :
DownloadManagerName :
LCMCompatibleVersions : {1.0, 2.0}
LCMState : Idle
LCMStateDetail :
LCMVersion : 2.0
StatusRetentionTimeInDays : 10
SignatureValidationPolicy : NONE
SignatureValidations : {}
MaximumDownloadSizeMB : 500
PartialConfigurations :
RebootNodeIfNeeded : False
RefreshFrequencyMins : 30
RefreshMode : PUSH
ReportManagers : {}
ResourceModuleManagers : {}
PSComputerName :
- RefreshMode содержит режим работы LCM — Push или Pull. О режимах мы поговорим подробнее во второй статье.
- ConfigurationMode показывает текущий режим применения конфигурации. В нашем случае это ApplyAndMonitor — применить и отслеживать изменения. Также доступны режимы ApplyOnly (LCM не будет отслеживать изменения конфигурации) и ApplyAndAutocorrect (LCM будет не только отслеживать изменения, но и откатывать их к базовой конфигурации).
- RebootNodeIfNeeded — может перезагружать сервер после завершения конфигурации, если это необходимо для применения настроек.
- ConfigurationModeFrequencyMins — фиксирует, как часто LCM будет проверять изменения конфигурации.
Настройки LCM меняем в метаконфигурации. Вот ее пример:
Configuration LCMConfiguration
{
Node Localhost
{
LocalConfigurationManager
{
RebootNodeIfNeeeded = $True
}
}
}
LCMConfiguration
То же самое для последней версии WMF с комментариями:
[DSCLocalConfigurationManager()]
Configuration LCMConfiguration
{
param
(
[string[]]$Server = "localhost"
)
# Настраиваем LCM:
# Перезагружать сервер, если нужно
# Режим: PUSH
# Обновлять состояние каждые 30 минут
Node $Server
{
Settings
{
RebootNodeIfNeeded = $True
RefreshMode = 'Push'
RefreshFrequencyMins = 30
}
}
}
# Старт компиляции метаконфигурации
LCMConfiguration -Server "localhost" -OutputPath "C:\DSC\MetaConfigurations\EnvironmentVariable_Path\"
Особенности метаконфигурации. При написании метаконфигурации мы используем те же блоки, что и для обычной конфигурации DSC. Исключение — внутренний блок LocalConfigurationManager (v4) или атрибут DSCLocalConfigurationManager (v5) для конкретного сервера. В них описываются все необходимые настройки.
Метаконфигурация тоже компилируется в mof-файл, но для ее применения используется командлет Set-DSCLocalConfigurationManager, а не Start-DSCConfiguration.
PS C:\windows\system32> LCMConfiguration -OutputPath C:\EnvironmentVariable_Path
Directory: C:\EnvironmentVariable_Path
Mode LastWriteTime Length Name
---- ------------- ------ ----
-a---- 26.02.2020 20:05 1016 Localhost.meta.mof
PS C:\windows\system32> Set-DscLocalConfigurationManager -Path C:\EnvironmentVariable_PathPS C:\windows\system32> Get-DscLocalConfigurationManager
…
RebootNodeIfNeeded : True
…
Теоретически ничто нам не мешает совмещать настройку LCM и обычных ресурсов в рамках одной конфигурации. Но для простоты рекомендуется написание и применение конфигурации и метаконфигурации разделять.
Частичные конфигурации
Частичные конфигурации (Partial Configurations) — несколько конфигураций, которые исполняются последовательно друг за другом. Они полезны в том случае, когда над сервисом работает несколько команд. Каждая из команд указывает настройки для своей части сервиса, а частичная конфигурация затем применяет все настройки последовательно. В настройках LCM мы указываем частичные конфигурации в PartialConfigurations.
В частичных конфигурациях мы должны учесть прямолинейную логику DSC. Движок не допускает ветвлений, так что для выполнения разных условий нужны дополнительные ресурсы. Разберем это на нескольких примерах.
Допустим, мы хотим гарантированно доставить конфигурацию на сервер и действуем по следующему алгоритму:
- Сначала проверяем, что нужные модули установлены на сервер.
- Выполняем конфигурацию, которая приведет сервер в нужное состояние.
Вот как выглядит метаконфигурация с несколькими последовательными конфигурациями внутри:
# Начало функции метаконфигурации
[DSCLocalConfigurationManager()]
configuration MetaPushConfig
{
param
(
[ValidateNotNullOrEmpty()]
[string] $NodeName = 'localhost'
)
Node $NodeName
{
# Описание первой конфигурации с указанием LCM, что конфигураций будет несколько
PartialConfiguration ModulesDownloadConfig
{
Description = 'Download and install modules'
RefreshMode = 'Push'
}
# Описание следующей конфигурации с указанием зависимости от первой
PartialConfiguration ServerOSConfig
{
DependsOn = "[PartialConfiguration]ModulesDownloadConfig"
Description = 'Configuration'
RefreshMode = 'Push'
}
# Глобальная настройка LCM
Settings
{
RefreshMode = 'Push'
RefreshFrequencyMins = 30
RebootNodeIfNeeded = $true
}
}
}
# Компиляция метаконфигурации
MetaPushConfig -NodeName "NewServer.contoso.com" -OutputPath c:\DSC\MetaConfigurations
# Учетные данные для обращения к серверу
$cred = (Get-Credential -UserName Administrator -Message "Enter admin credentials")
# Конфигурирование LCM на целевом сервере
Set-DscLocalConfigurationManager -ComputerName "NewServer.contoso.com" -Credential $cred -Path "c:\DSC\MetaConfigurations" -Verbose -Force
# Публикация конфигураций
Publish-DscConfiguration c:\DSC\Configurations\ModulesDownloadConfig -ComputerName "NewServer.contoso.com" -Credential $cred -Force
Publish-DscConfiguration c:\DSC\Configurations\ServerOSConfig -ComputerName "NewServer.contoso.com" -Credential $cred -Force
# Запуск конфигураций на исполнение
Start-DscConfiguration -UseExisting -ComputerName "NewServer.contoso.com" -Credential $cred -Force -Verbose
В первой части конфигурации указано Download and install modules: скачай и установи необходимые ресурсы. Вторая часть ServerOSConfig приводит сервер к нужному состоянию. Какие здесь могут быть нюансы с прямолинейностью DSC:
- Если первая часть конфигурации изначально вернула значение FALSE и выполнилась полностью, то LCM не перейдет ко второй конфигурации. По логике DSC, сначала нужно привести сервер к первому описанному состоянию.
Как лечим: запускаем конфигурирование дважды или автоматизируем весь процесс в скрипте. - Если сервер после установки какого-либо компонента требует перезагрузки, то конфигурация дальше не пойдет, пока мы сами не перезагрузим сервер. Даже если мы укажем LCM, что RebootNodeIfNeeeded = $True, то во время конфигурации агент будет ожидать нашего решения.
Как лечим: на помощь приходит ресурс xPendingReboot, который контролирует ключ реестра по перезагрузке. Этот ресурс перезагружает сервер за нас.
Приведем пример конфигурации для скачивания и установки ресурсов в сценарии «кровавый энтерпрайз», когда сервер не имеет выхода в интернет. Здесь предполагается наличие веб-сервера, где заранее скачанные ресурсы доступны всем по протоколу http.
Configuration ModulesDownloadConfig
{
param
(
[string[]]$Server
)
# импорт базового ресурса
Import-DscResource -ModuleName "PSDesiredStateConfiguration"
# Конфигурация сервера
Node $Server
{
# Отключаем IE Security
Registry DisableIEESC-Admin {
Key = "HKLM:\SOFTWARE\Microsoft\Active Setup\Installed Components\{A509B1A7-37EF-4b3f-8CFC-4F3A74704073}"
ValueName = "IsInstalled"
Ensure = "Present"
ValueData = 0
ValueType = "DWORD"
}
Registry DisableIEESC-User {
Key = "HKLM:\SOFTWARE\Microsoft\Active Setup\Installed Components\{A509B1A8-37EF-4b3f-8CFC-4F3A74704073}"
ValueName = "IsInstalled"
Ensure = "Present"
ValueData = "0"
ValueType = "DWORD"
}
# Создаем папку для ресурсов, если ее не существует
File CreateDistribDir {
Ensure = "present"
DestinationPath = "C:\Install\PSModules"
Type = "Directory"
}
# Скачиваем ресурс NetworkingDsc (<a href="https://www.powershellgallery.com/packages/NetworkingDsc/8.0.0-preview0004">https://www.powershellgallery.com/packages/NetworkingDsc/8.0.0-preview0004</a>), если его нет в локальном каталоге
Script NetworkingDscDownLoad {
SetScript = { Invoke-WebRequest -Uri "http://repo.contoso.com/repo/modules/NetworkingDsc.zip" -OutFile "C:\Install\PSModules\NetworkingDsc.zip" }
GetScript = { return @{ Result = Test-Path "C:\Install\PSModules\NetworkingDsc.zip"
GetScript = $GetScript; SetScript = $SetScript; TestScript = $TestScript
}
}
TestScript = { Test-Path "C:\Program Files\WindowsPowerShell\Modules\NetworkingDsc" }
}
# Распаковываем ресурс NetworkingDsc в C:\Program Files\WindowsPowerShell\Modules
Archive UnpackNetworkingDsc {
Ensure = "Present"
DependsOn = "[Script]NetworkingDscDownLoad"
Path = "C:\Install\PSModules\NetworkingDsc.zip"
Destination = "C:\Program Files\WindowsPowerShell\Modules\"
}
# Скачиваем ресурс ComputerManagementDsc (<a href="https://www.powershellgallery.com/packages/ComputerManagementDsc/8.2.1-preview0001">https://www.powershellgallery.com/packages/ComputerManagementDsc/8.2.1-preview0001</a>), если его нет в локальном каталоге
Script ComputerManagementDscDownLoad {
SetScript = { Invoke-WebRequest -Uri "http://repo.contoso.com/repo/modules/ComputerManagementDsc.zip" -OutFile "C:\Install\PSModules\ComputerManagementDsc.zip" }
GetScript = { return @{ Result = Test-Path "C:\Install\PSModules\ComputerManagementDsc.zip"
GetScript = $GetScript; SetScript = $SetScript; TestScript = $TestScript
}
}
TestScript = { Test-Path "C:\Program Files\WindowsPowerShell\Modules\ComputerManagementDsc" }
}
# Распаковываем ресурс ComputerManagementDsc в C:\Program Files\WindowsPowerShell\Modules
Archive UnpackComputerManagementDsc {
Ensure = "Present"
DependsOn = "[Script]ComputerManagementDscDownLoad"
Path = "C:\Install\PSModules\ComputerManagementDsc.zip"
Destination = "C:\Program Files\WindowsPowerShell\Modules\"
}
# Скачиваем ресурс xPendingReboot (<a href="https://www.powershellgallery.com/packages/xPendingReboot/0.4.0.0">https://www.powershellgallery.com/packages/xPendingReboot/0.4.0.0</a>), если его нет в локальном каталоге
Script xPendingRebootDownLoad {
SetScript = { Invoke-WebRequest -Uri "http://repo.contoso.com/repo/modules/xPendingReboot.zip" -OutFile "C:\Install\PSModules\xPendingReboot.zip" }
GetScript = { return @{ Result = Test-Path "C:\Install\PSModules\xPendingReboot.zip"
GetScript = $GetScript; SetScript = $SetScript; TestScript = $TestScript
}
}
TestScript = { Test-Path "C:\Program Files\WindowsPowerShell\Modules\xPendingReboot" }
}
# Распаковываем ресурс xPendingReboot в C:\Program Files\WindowsPowerShell\Modules
Archive UnpackxPendingReboot {
Ensure = "Present"
DependsOn = "[Script]xPendingRebootDownLoad"
Path = "C:\Install\PSModules\xPendingReboot.zip"
Destination = "C:\Program Files\WindowsPowerShell\Modules\"
}
}
}
Про безопасность
Жирным минусом DSC на «земле» является отсутствие Run As Accounts. Этот механизм безопасно хранит учетные записи в виде логин+«соленый хэш пароля» и подсовывает их сервису, который отвечает за аутентификацию. Без него DSC не может управлять учетными записями от имени другого сервиса. А если нам нужно аутентифицироваться под учетной записью с особыми привилегиями, процесс автоматизации сильно осложняется. Например, так будет при вводе сервера в домен.
Все, что есть в нашем распоряжении:
- учетные данные хранятся в Plain Text,
- шифрованные учетные данные работают только на том ПК, где они были сгенерированы.
На практике есть несколько вариантов решения.
- Использовать Azure Automation Run As Accounts и завязать наши конфигурации на движок Azure Automation State Configuration. В этом сценарии мы сохраним безопасность при передаче учетных данных и простоту применения конфигураций к целевым виртуальным машинам за счет «черной магии» сервисов Azure. Однако, использование Run As аккаунтов в On-Premise сильно усложнит инфраструктуру решения. Поэтому в условиях «кровавого энтерпрайза» данный подход малоприменим, хоть и возможен.
- После применения конфигурации DSC сделать конфигурацию-заглушку, которая не содержит хэш пароля и исключает компрометирование учетных данных.
«Заглушки» хороши, когда должна быть обеспечена предсказуемость поведения сервера.
Мы делаем «заглушку» после конфигурации сервера в нескольких случаях, если мы использовали:
- перезагрузку (ресурс xPendingReboot),
- передачу учетных данных,
- другие ресурсы, которые могут повлиять на работу сервера в части незапланированной перезагрузки или безопасности.
Для этого создадим и переопубликуем конфигурации БЕЗ блоков, содержащих ресурс xPendingReboot и конфигурации с учетными данными.
Подобный подход применим только к сценарию Push, поскольку Pull в достаточной степени прямолинеен и не подразумевает замену конфигурации на лету. В следующей статье от buldakov мы в деталях рассмотрели настройку и особенности работы в режимах Push и Pull.
А еще приходите обсудить нюансы технологии DSC и границы ее применения 28 мая в 18.00 на первом онлайн-митапе сообщества DGTL Communications Райффайзенбанка. Также на встрече поговорим, как подружить ELK и Exchange и что может Microsoft Endpoint Manager в управлении устройствами. Вот тут регистрация на митап.
amarao
Я очень не люблю Ансибл за рыхлость и душераздирающую типизацию (и модель памяти), но на фоне ваших примеров, Ансибл — вершина совершенства. Вот это надо закопать и забыть.
Sayanaro Автор
Не совсем корректно сравнивать DSC и Ансибл. Каждый инструмент хорош для своей задачи и в своем контексте. Ну и Ансибл на винде — это боль.
snd3r
Почему не корректно? Вполне корректно — обе системы отвечают за управление конфигурацией.
Другое дело, что у вас какие-то накуренные конфигурации, корнеркейсы не для энтри-левел, они относительно "простого декларативного" ансибла выглядят крипово.
А вообще мне DSC нравится больше чем ансибл, как минимум тем, что он при достаточной декларативности хешмапов вполне императивен и можно быстренько без модуля самому описать три блока прямо внутри конфигурации:
Sayanaro Автор
Скрипт-блоки — прелесть. Но с модулями удобнее (хоть и не всегда, так как порой логика исполнения ресурсов кривая).
Про некорректное сравнение: DSC в какой-то степени деревянный. Но зато работает на любом утюге с пошиком.)) А еще у нас есть SCCM и Terraform, которые тоже приводят систему к нужному состоянию. Но DSC все же примитивнее их всех)
snd3r
А я еще и с Orchestrator, в дополнение к sccm, работал и вспоминаю все это как страшный сон времен vbs скриптов в сисволе.
Хорошо когда есть ps и dsc. Терраформ тоже хорошо наверное, но у меня нет таких задач.
Sayanaro Автор
Терраформ далеко не со всеми решениями работает корректно: SC VMM он плохо деплоит)
Итого — у каждой задачи свой набор инструментов из огромного зоопарка))