预期状态设定
Desired State Configuration
何为预期状态设定?
预期状态设定,Desired State Configuration,简称 DSC,是内建于 PowerShell 的一个工具,可用于通过代码定义 Windows 主机设置。DSC 的总体目的与 Ansible 相同,只是被执行方式不同。自版本 2.4 起,Ansible 就添加了 win_dsc
模组,可在与 Windows 主机交互时,利用现有的 DSC 资源。
有关 DSC 的更多详细信息,请访问 DSC 概述。
参考:
主机要求
要使用 win_dsc
这个模组,Windows 主机必须有安装 PowerShell v5.0 或更新版本。所有受支持的主机,都可以升级到 PowerShell v5。
一旦 PowerShell 的要求已满足,那么使用 DSC 就跟使用 win_dsc
模组创建某个任务这么简单了。
为何要用 DSC?
DSC 和 Ansible 的那些模组,有着共同的目标,那就是定义并确保某项资源的状态。因此,像是 DSC 文件资源 与 Ansible win_file
这样的资源,可用于达成同一结果。而决定要使用哪个,则取决于具体场景。
使用 Ansible 模组而非 DSC 资源的理由:
- 主机不支持 PowerShell v5.0,或无法轻易被升级;
- DSC 资源未提供某个 Ansible 模组中存在的某项功能。例如,
win_regedit
可以管理REG_NONE
的属性类型,而 DSC 的Registry
资源则不能; - DSC 资源的检查模式支持有限,而一些 Ansible 模组则有着更好的检查;
- DSC 资源不支持差异模式,diff mode,而一些 Ansible 模组则支持;
- 一些自定义资源要运行在主机上,需要事先的一些进一步安装步骤,而 Ansible 模组则内置在 Ansible 中;
- Ansible 模组可工作的某个 DSC 资源中存在 bug。
使用 DSC 资源而非 Ansible 模组的原因:
- Ansible 模组不支持,但某个 DSC 资源中存在的某项功能;
- 尚无可用的 Ansible 模组;
- 现有的某个 Ansible 模组中存在 bug。
归根结底,是以 DSC 还是以 Ansible 模组,执行某项任务并不重要,重要的是该任务被正确执行,且 playbook 仍然可读。若咱们 DSC 的经验比 Ansible 更多,而且他会完成任务,那就使用 DSC 完成该任务。
怎样使用 DSC?
win_dsc
模组接收自由格式的选项,以便根据其所管理的资源而变通。可在 资源 处找到一个内置资源的列表。
以 Registry
这个资源为例,以下是微软文档中的 DSC 定义:
Registry [string] #ResourceName
{
Key = [string]
ValueName = [string]
[ Ensure = [string] { Enable | Disable } ]
[ Force = [bool] ]
[ Hex = [bool] ]
[ DependsOn = [string[]] ]
[ ValueData = [string[]] ]
[ ValueType = [string] { Binary | Dword | ExpandString | MultiString | Qword | String } ]
}
定义任务时,resource_name
字段必须设置为正使用的 DSC 资源 -- 在此情形下,resource_name
应设置为 Registry
。module_version
字段可指向该已安装 DSC 资源的特定版本;若该字段留空,则默认为最新版本。其他选项为用于定义该资源的一些参数,比如 Key
与 ValueName
。虽然该任务中的选项不区分大小写,但建议保持大小写不变,因为这样更容易将 DSC 的资源选项,与 Ansible 的 win_dsc
选项区分开来。
以下是上述 DSC Registry
资源的 Ansible 任务版本,可能的样子:
- name: Use win_dsc module with the Registry DSC resource
win_dsc:
resource_name: Registry
Ensure: Present
Key: HKEY_LOCAL_MACHINE\SOFTWARE\ExampleKey
ValueName: TestValue
ValueData: TestData
译注:在针对 Windows 10 IoT Enterprise LTSC 21H2 19044.5487,执行包含该任务的 playbook,
ansible-playbook -i playbook_executing/inventory.yml playbook_executing/dsc_demo.yml
时,报出了错误:System.Management.Automation.ItemNotFoundException: Cannot find path 'WSMan:\localhost\Client\DefaultPorts\HTTP' because it does not exist.
。在询问 Deepseek 后,发现需要开启 WinRM 服务。
Enable-PSRemoting -Force
New-PSDrive -Name WSMan -PSProvider WSMan -Root "\" -ErrorAction SilentlyContinue
New-Item -Path "WSMan:\localhost\Client\DefaultPorts\HTTP" -Force
检查 WinRM 服务状态:
Get-Service WinRM # 确保状态为 "Running":ml-citation{ref="1,4" data="citationList"}
防火墙放行 WinRM:
Enable-NetFirewallRule -Name "WINRM-HTTP-In-TCP"
快速设置
WSMan
:
Set-WSManQuickConfig
随后上述报错消失,但出现新的报错:
Microsoft.Management.Infrastructure.CimException: Not found
。随后运行[System.Reflection.Assembly]::LoadFrom("Microsoft.Management.Infrastructure.dll")
,报出"Could not load file or assembly 'file:///C:\Windows\system32\Microsoft.Management.Infrastructure.dll' or one of its dependencies. The system cannot find the file specified."
错误。
故疑似目标 Windows 10 IoT Enterprise LTSC 缺少 DSC/WinRM 的完整支持,后续下载安装 Windows server 2019 再进行测试。后面进一步分析此问题,发现 Windows IoT Enterprise LTSC 上的 PowerShell 版本为 5.1 (查看 PowerShell 版本:
$PSVersionTable.PSVersion
),DSC 版本为 1.1。更高版本的 DSC 2.0.7 无法运行在 PowerShell 5.1 上。为此,尝试升级到 PowerShell 7.5.0,并运行命令Install-Module -Name PSDesiredStateConfiguration -Repository PSGallery -MaximumVersion 2.99
安装 DSC2。但是,尽管安装了 PowerShell 7.5.0 与 DSC 2.0.7,却无法通过
Import-Module PSDesiredStateConfiguration
在 Powershell 5.1 中加载。而 Powershell 5.1 是 Windows 10 系统的一部分,而无法将 Windows 10 的默认 PowerShell 切换到 7.5.0 版本。
因此,必需在 PowerShell 5.1 与 DSC 1.1 的基础上,解决此问题。最后在将目标机器从 Windows 10 IoT Enterprise LTSC 变更为 Server 2019 后,上面的 playbook 顺利运行。
更新:在原来无法运行
win_dsc
模组的 Windows 10 IoT Enterprise LTSC 主机上,运行了 Windows 性能 小节中的 PowerShell 脚本后,就可以执行该模组了!这说明该脚本不仅优化了 PowerShell 性能,还修复了原来的问题。参考:
从 Ansible 2.8 开始,win_dsc
模组会自动以 DSC 定义,验证来自 Ansible 的输入选项。这意味着在选项名字不正确、某个强制选项未设置或值不是个有效选择时,Ansible 就会失败。在以输出信息级别 3 或更高 (-vvv
) 运行 Ansible 时,返回值就将包含根据所指定的 resource_name
,而可能的那些调用选项。下面是以上 Registry
任务的调用输出示例:
changed: [win2k19-151] => {
"changed": true,
"invocation": {
"module_args": {
"DependsOn": null,
"Ensure": "Present",
"Force": true,
"Hex": null,
"Key": "HKEY_LOCAL_MACHINE\\SOFTWARE\\ExampleKey",
"PsDscRunAsCredential_password": null,
"PsDscRunAsCredential_username": null,
"ValueData": [
"TestData"
],
"ValueName": "TestValue",
"ValueType": null,
"module_version": "latest",
"resource_name": "Registry" } },
"module_version": "1.1",
"reboot_required": false,
"verbose_set": [
"Perform operation 'Invoke CimMethod' with following parameters, ''methodName' = ResourceSet,'className' = MSFT_DSCLocalConfigurationManager,'names
paceName' = root/Microsoft/Windows/DesiredStateConfiguration'.",
"An LCM method call arrived from computer WIN-80VGLU6IKPV with user sid S-1-5-21-190744080-1005738922-3847091905-500.",
"[WIN-80VGLU6IKPV]: LCM: [ Start Set ] [[Registry]DirectResourceAccess]",
"[WIN-80VGLU6IKPV]: [[Registry]DirectResourceAccess] (SET) Create registry key 'HKLM:\\SOFTWARE\\ExampleKey'",
"[WIN-80VGLU6IKPV]: [[Registry]DirectResourceAccess] (SET) Set registry key value 'HKLM:\\SOFTWARE\\ExampleKey\\TestValu
e' to 'TestData' of type 'String'",
"[WIN-80VGLU6IKPV]: LCM: [ End Set ] [[Registry]DirectResourceAccess] in 0.1560 seconds.",
"[WIN-80VGLU6IKPV]: LCM: [ End Set ] in 0.2030 seconds.",
"Operation 'Invoke CimMethod' complete.",
"Time taken for configuration job to complete is 0.291 seconds"
],
"verbose_test": [
"Perform operation 'Invoke CimMethod' with following parameters, ''methodName' = ResourceTest,'className' = MSFT_DSCLocalConfigurationManager,'name
spaceName' = root/Microsoft/Windows/DesiredStateConfiguration'.",
"An LCM method call arrived from computer WIN-80VGLU6IKPV with user sid S-1-5-21-190744080-1005738922-3847091905-500.",
"[WIN-80VGLU6IKPV]: LCM: [ Start Test ] [[Registry]DirectResourceAccess]",
"[WIN-80VGLU6IKPV]: [[Registry]DirectResourceAccess] Registry key 'HKLM:\\SOFTWARE\\ExampleKey' does not exist",
"[WIN-80VGLU6IKPV]: LCM: [ End Test ] [[Registry]DirectResourceAccess] False in 0.7500 seconds.",
"[WIN-80VGLU6IKPV]: LCM: [ End Set ] in 0.8120 seconds.",
"Operation 'Invoke CimMethod' complete.",
"Time taken for configuration job to complete is 0.917 seconds"
]
}
其中的 invocation.module_args
键,就显示了那些已设置的具体值,及未设置的其他可能值。遗憾的是,这不会显示某个 DSC 属性的默认值,而只有该 Ansible 任务中设置的值。出于安全考虑,任何 *_password
选项,都将在输出中屏蔽;若有任何别的敏感模组选项,请在该任务上设置 no_log: true
,以停止记录所有任务输出。
属性类型
每个 DSC 资源属性,都有种与之关联的类型。在执行过程中,Ansible 会尝试将所定义的选项,转换为正确的类型。对于像是 [string]
和 [bool]
等简单类型,这属于一个简单的操作,但像是 [PSCredential]
或数组(比如 [string[]]
)等复杂类型,则需要一定的规则。
PSCredential
某个 [PSCredential]
对象,用于以安全方式存储凭证,但 Ansible 没有通过 JSON 将其序列化的办法。要设置某个 DSC 的 PSCredential
属性,该参数的定义,应包含用于表示用户名和密码的,后缀分别为 _username
和 _password
的条目。例如:
PsDscRunAsCredential_username: '{{ ansible_user }}'
PsDscRunAsCredential_password: '{{ ansible_password }}'
SourceCredential_username: AdminUser
SourceCredential_password: PasswordForAdminUser
注意:在 2.8 以上版本的 Ansible 中,咱们应在 Ansible 种的任务定义上,设置
no_log:true
,以确保用到的全部凭证,不会存储在任何日志文件或控制台输出中。
[PSCredential]
属性,是以 EmbeddedInstance(“MSFT_Credential”)
,并以某项 DSC 资源的 MOF 定义格式定义的。
译注:'a DSC resource MOF definition',其中 'MOF' 指的是 Managed Object Format,托管对象格式。
参考:
CimInstance
类型
DSC 使用 [CimInstance]
对象,存储基于该资源所定义的自定义类的字典对象。在 YAML 中定义某个取 [CimInstance]
类型的值,与在 YAML 中定义某个字典相同。例如,要在 Ansible 中定义一个 [CimInstance]
的值:
# [CimInstance]AuthenticationInfo == DSC_WebAuthenticationInformation
AuthenticationInfo:
Anonymous: false
Basic: true
Digest: false
Windows: true
在上面的示例中,其中的 CIM 实例便是类 DSC_WebAuthenticationInformation
的表示。这个类接受四个布尔值变量:Anonymous
、Basic
、Digest
与 Windows
。在某个 [CimInstance]
的值中使用的键,取决于其所表示的类。请阅读资源的文档,确定出可使用的键及各个键值的类型。类的定义通常位于 <resource name>.schema.mof
这个文件中。
HashTable
类型
[HashTable]
对象也是个字典,不过他并无一组严格的键值可以/需要定义。与 [CimInstance]
一样,在 YAML 中将其定义为一般字典值即可。[HashTable]]
是以 EmbeddedInstance(“MSFT_KeyValuePair”)
,以某项 DSC 资源的 MOF 定义格式定义的。
- 数组
像 [string[]]
或 [UInt32[]]
这样的简单类型数组,会被定义为列表或逗号分隔的字符串,然后再将其转换为他们的类型。建议使用列表,因为在将值传递给 DSC 引擎前,这些值不会被 win_dsc
模组手动解析(?)。例如,要在 Ansible 中定义某个简单类型的数组:
# [string[]]
ValueData: entry1, entry2, entry3
ValueData:
- entry1
- entry2
- entry3
# [UInt32[]]
ReturnCode: 0,3010
ReturnCode:
- 0
- 3010
像 [CimInstance[]]
(字典数组)这样的复杂类型数组,可以像下面这个示例这样定义:
# [CimInstance[]]BindingInfo == DSC_WebBindingInformation
BindingInfo:
- Protocol: https
Port: 443
CertificateStoreName: My
CertificateThumbprint: C676A89018C4D5902353545343634F35E6B3A659
HostName: DSCTest
IPAddress: '*'
SSLFlags: 1
- Protocol: http
Port: 80
IPAddress: '*'
上面的示例是个带有两个 DSC_WebBindingInformation
类值的数组。在定义某个 [CimInstance[]]
时,请务必阅读资源文档,了解在定义中要使用哪些键。
DateTime
[DateTime]
对象是个以 ISO 8601 日期时间格式,表示日期和时间的 DateTime
字符串。某 [DateTime]
字段的值应在 YAML 中以单引号括起来,以确保该字符串能恰当地序列化到 Windows 主机。下面是个如何在 Ansible 中,定义 [DateTime]
值的示例:
# As UTC-0 (No timezone)
DateTime: '2019-02-22T13:57:31.2311892+00:00'
# As UTC+4
DateTime: '2019-02-22T17:57:31.2311892+04:00'
# As UTC-4
DateTime: '2019-02-22T09:57:31.2311892-04:00'
上面的所有值,都等于 UTC 日期时间 2019 年 2 月 22 日下午 1:57 的 31 秒 2311892 毫秒。
以另一用户运行
默认情况下,DSC 会以 SYSTEM
账户,而非 Ansible 用以运行该模组的账户,运行各项资源。这意味着这些资源会根据用户配置文件,被动态地加载,比如 HKEY_CURRENT_USER
这个注册表项目,就将在 SYSTEM
的配置文件下加载。参数 PsDscRunAsCredential
是个可对每项 DSC 资源设置,并强制 DSC 引擎在别的账户下运行的参赛。由于 PsDscRunAsCredential
的类型为 PSCredential
,因此要以 _username
和 _password
后缀定义。
以 Registry
资源类型为例,以下便是定义一个访问 Ansible 用户的 HKEY_CURRENT_USER
项目的方式:
- name: Use win_dsc with PsDscRunAsCredential to run as a different user
win_dsc:
resource_name: Registry
Ensure: Present
Key: HKEY_CURRENT_USER\ExampleKey
ValueName: TestValue
ValueData: TestData
PsDscRunAsCredential_username: '{{ ansible_user }}'
PsDscRunAsCredential_password: '{{ ansible_password }}'
no_log: true
定制 DSC 资源
DSC 资源并不仅限于微软的那些内置选项。可以安装一些定制模组,来管理通常不可用的其他资源。
查找定制 DSC 资源
咱们可使用 PSGallery
,查找定制资源,以及如何在 Windows 主机上安装这些资源的文档。
Find-DscResource
这个cmdlet 也可用于查找定制资源。例如:
# Find all DSC resources in the configured repositories
Find-DscResource
# Find all DSC resources that relate to SQL
Find-DscResource -ModuleName "*sql*"
注意:由 Microsoft 开发的以
x
开头的 DSC 资源,表示该资源是试验性的,不提供任何支持。
安装某个定制资源
可以三种方式,将某个 DSC 资源安装在主机上:
- 手动使用
Install-Module
这个 cmdlet; - 使用
win_psmodule
这个 Ansible 模组; - 手动保存该模组,并将其拷贝到另一主机上。
以下是个使用 win_psmodule
安装 xWebAdministration
资源的示例:
- name: Install xWebAdministration DSC resource
win_psmodule:
name: xWebAdministration
state: present
一旦某项资源安装后,win_dsc
模组就可以 resource_name
选项,引用该资源。
上面的前两种方法,只有在主机可以访问互联网时才会工作。当主机没有访问互联网时,就必须先在一台有访问互接入的主机上,使用上述方式安装模组,然后将该模组复制到另一主机上。要将某个模组保存到本地文件路径,可运行以下 PowerShell cmdlet:
Save-Module -Name xWebAdministration -Path C:\temp
这将在 C:\temp
中创建一个名为 xWebAdministration
的文件夹,其可被复制到任何主机上。要让 PowerShell 看到此离线资源,必须将其复制到设置在 PSModulePath
环境变量中的目录。在大多数情况下,通过该变量设置的是 C:\Program Files\WindowsPowerShell\Module
,但可使用 win_path
添加别的路径。
示例
提取某个 zip 文件
- name: Extract a zip file
win_dsc:
resource_name: Archive
Destination: C:\temp\output
Path: C:\temp\zip.zip
Ensure: Present
创建目录
- name: Create file with some text
win_dsc:
resource_name: File
DestinationPath: C:\temp\file
Contents: |
Hello
World
Ensure: Present
Type: File
- name: Create directory that is hidden is set with the System attribute
win_dsc:
resource_name: File
DestinationPath: C:\temp\hidden-directory
Attributes: Hidden,System
Ensure: Present
Type: Directory
与 Azure 交互
- name: Install xAzure DSC resources
win_psmodule:
name: xAzure
state: present
- name: Create virtual machine in Azure
win_dsc:
resource_name: xAzureVM
ImageName: a699494373c04fc0bc8f2bb1389d6106__Windows-Server-2012-R2-201409.01-en.us-127GB.vhd
Name: DSCHOST01
ServiceName: ServiceName
StorageAccountName: StorageAccountName
InstanceSize: Medium
Windows: true
Ensure: Present
Credential_username: '{{ ansible_user }}'
Credential_password: '{{ ansible_password }}'
设置 IIS Web 站点
- name: Install xWebAdministration module
win_psmodule:
name: xWebAdministration
state: present
- name: Install IIS features that are required
win_dsc:
resource_name: WindowsFeature
Name: '{{ item }}'
Ensure: Present
loop:
- Web-Server
- Web-Asp-Net45
- name: Setup web content
win_dsc:
resource_name: File
DestinationPath: C:\inetpub\IISSite\index.html
Type: File
Contents: |
<html>
<head><title>IIS Site</title></head>
<body>This is the body</body>
</html>
Ensure: present
- name: Create new website
win_dsc:
resource_name: xWebsite
Name: NewIISSite
State: Started
PhysicalPath: C:\inetpub\IISSite\index.html
BindingInfo:
- Protocol: https
Port: 8443
CertificateStoreName: My
CertificateThumbprint: C676A89018C4D5902353545343634F35E6B3A659
HostName: DSCTest
IPAddress: '*'
SSLFlags: 1
- Protocol: http
Port: 8080
IPAddress: '*'
AuthenticationInfo:
Anonymous: false
Basic: true
Digest: false
Windows: true
(End)