教程:创建基于类的 DSC 资源

开始创作基于类的 DSC 资源来管理配置文件。 完成本教程后,你将在可用于进一步学习和自定义的模块中提供基于功能类的 DSC 资源。

在本教程中,你将了解如何执行以下操作:

  • 搭建 DSC 资源模块基架
  • 添加基于类的 DSC 资源
  • 定义 DSC 资源属性
  • 实现 DSC 资源方法
  • 导出模块清单中的 DSC 资源
  • 手动测试 DSC 资源

注意

本教程中的示例输出与 Windows 计算机上的 PowerShell 7.2 匹配。 本教程适用于 Linux 或 macOS 计算机上的 Windows PowerShell 和 PowerShell。 只有输出特定于在 Windows 计算机上在 PowerShell 中运行命令。

先决条件

  • PowerShell 或 Windows PowerShell 5.1
  • 使用 PowerShell 扩展的 VS Code

1 - 搭建 DSC 资源模块的基架

必须在 PowerShell 模块中定义 DSC 资源。

创建模块文件夹

创建名为 ExampleResources的新文件夹。 此文件夹用作本教程中模块和所有代码的根文件夹。

New-Item -Path './ExampleResources' -ItemType Directory
    Directory: C:\code\dsc

Mode                 LastWriteTime         Length Name
----                 -------------         ------ ----
d----            9/8/2022 12:54 PM                ExampleResources

使用 VS Code 创作模块

在 VS Code 中打开 ExampleResources 文件夹。 在 VS Code 中打开集成终端。 确保终端正在运行 PowerShell 或 Windows PowerShell。

重要

对于本教程的其余部分,请在模块文件夹根目录中的集成终端中运行指定的命令。 这是 VS Code 中的默认工作目录。

创建模块文件

使用 New-ModuleManifest cmdlet 创建模块清单。 用作./ExampleResources.psd1路径。 将 RootModule 指定为 ExampleResources.psm1将 DscResourcesToExport 指定为 Tailspin

$ModuleSettings = @{
    RootModule           = 'ExampleResources.psm1'
    DscResourcesToExport = 'Tailspin'
}

New-ModuleManifest -Path ./ExampleResources.psd1 @ModuleSettings
Get-Module -ListAvailable -Name ./ExampleResources.psd1 | Format-List
Name              : ExampleResources
Path              : C:\code\dsc\ExampleResources\ExampleResources.psd1
Description       :
ModuleType        : Script
Version           : 0.0.1
PreRelease        :
NestedModules     : {}
ExportedFunctions :
ExportedCmdlets   :
ExportedVariables :
ExportedAliases   :

将根模块文件创建为 ExampleResources.psm1

New-Item -Path ./ExampleResources.psm1
    Directory: C:\code\dsc\ExampleResources

Mode                 LastWriteTime         Length Name
----                 -------------         ------ ----
-a---            9/8/2022  1:57 PM              0 ExampleResources.psm1

创建名为 的 Helpers.ps1脚本文件。

New-Item -Path ./Helpers.ps1
    Directory: C:\code\dsc\ExampleResources

Mode                 LastWriteTime         Length Name
----                 -------------         ------ ----
-a---            9/8/2022  1:58 PM              0 Helpers.ps1

在 VS Code 中打开 Helpers.ps1 。 添加以下行,如果使用的是 Windows,:并且使用的是 Linux 或 macOS,请将 替换为 。<Separator>;

$env:PSModulePath += "<Separator>$pwd"

在 VS Code 中打开 ExampleResources.psm1 。 模块现已搭建基架,可供你创作 DSC 资源。

2 - 添加基于类的 DSC 资源

为了定义基于类的 DSC 资源,我们在模块文件中编写一个 PowerShell 类,并向其添加 DscResource 属性。

定义类

ExampleResources.psm1 中添加以下代码:

[DscResource()]
class Tailspin {

}

此代码将作为基于类的 DSC 资源添加到 TailspinExampleResources 模块。

将鼠标悬停在上 [DscResource()] 并读取警告。

VS Code 中 DSCResource 属性警告的屏幕截图。

将鼠标悬停在 DSCResource 属性上会显示四条警告。 1. DSC 资源“Tailspin”缺少返回“[void]”且不接受任何参数的“Set () ”方法。 2. DSC 资源“Tailspin”缺少返回“[Tailspin]”且不接受任何参数的“Get () ”方法。 3. DSC 资源“Tailspin”缺少返回“[bool]”且不接受任何参数的“Test () ”方法。 4. DSC 资源“Tailspin”必须使用语法“[DscProperty (Key) ]” (至少一个键属性。)

这些警告列出了类为有效 DSC 资源的要求。

最小实现所需的方法

将 、 Test()Set() 方法的Get()最小实现添加到 类。

class Tailspin {
    [Tailspin] Get() {
        $CurrentState = [Tailspin]::new()
        return $CurrentState
    }

    [bool] Test() {
        return $true
    }

    [void] Set() {}
}

添加方法后, DscResource 属性仅警告类没有 Key 属性。

3 - 定义 DSC 资源属性

应在方法之前定义 DSC 资源的属性。 属性定义 DSC 资源的可管理设置。 它们在 方法中使用。

了解 TSToy 应用程序

在定义 DSC 资源的属性之前,需要了解要管理的设置。

在本教程中,我们将定义一个 DSC 资源,用于通过其配置文件管理虚构 TSToy 应用程序的设置。 TSToy 是一个在用户和计算机级别具有配置的应用程序。 DSC 资源应能够配置任一文件。

DSC 资源应使用户能够配置:

  • 他们正在管理的配置的范围,或MachineUser
  • 配置文件是否应存在
  • TSToy 是否应自动更新
  • TSToy 应检查更新的频率(介于 1 到 90 天之间)

添加 ConfigurationScope 属性

若要管理 MachineUser 配置文件,需要定义 DSC 资源的 属性。 若要在资源中定义 $ConfigurationScope 属性,请在 方法前的 类中添加以下代码:

[DscProperty(Key)] [TailspinScope]
$ConfigurationScope

此代码将 $ConfigurationScope 定义为 DSC 资源的 Key 属性。 Key 属性用于唯一标识 DSC 资源的实例。 添加此属性满足 DscResource 属性在搭建类基架时警告的要求之一。

它还指定 $ConfigurationScope的类型为 TailspinScope。 若要定义 TailspinScope 类型,请在 中的ExampleResources.psm1类定义之后添加以下 TailspinScope 枚举:

enum TailspinScope {
    Machine
    User
}

此枚举使 MachineUser DSC 资源的 属性的唯一有效值 $ConfigurationScope

添加 Ensure 属性

最佳做法是定义属性 $Ensure 来控制是否存在 DSC 资源的实例。 属性通常有两个 $Ensure 有效值, AbsentPresent

  • 如果 $Ensure 指定为 Present,则 DSC 资源将创建项(如果不存在)。
  • 如果 $EnsureAbsent,则 DSC 资源会删除该项(如果存在)。

对于 Tailspin DSC 资源,要创建或删除的项是指定 $ConfigurationScope的配置文件。

TailspinEnsure 定义为 TailspinScope 后面的枚举。 它应具有 值 AbsentPresent

enum TailspinEnsure {
    Absent
    Present
}

接下来,在 $Ensure 类中将 属性添加到 属性的 $ConfigurationScope 后面。 它应具有空 的 DscProperty 属性,其类型应为 TailspinEnsure。 它应默认为 Present

[DscProperty()] [TailspinEnsure]
$Ensure = [TailspinEnsure]::Present

添加 UpdateAutomatically 属性

若要管理自动更新,请在 $UpdateAutomatically 类中定义 属性后的 $Ensure 属性。 其 DscProperty 属性应指示它是必需的,其类型应为 布尔值

[DscProperty(Mandatory)] [bool]
$UpdateAutomatically

添加 UpdateFrequency 属性

若要管理 TSToy 应检查更新的频率,请在 $UpdateFrequency 类中的 属性后面 $UpdateAutomatically 添加 属性。 它应具有空 的 DscProperty 属性,其类型应为 int。使用 ValidateRange 属性将 的有效 $UpdateFrequency 值限制在 1 到 90 之间。

[DscProperty()] [int] [ValidateRange(1, 90)]
$UpdateFrequency

添加隐藏的缓存属性

接下来,添加两个用于缓存资源的当前状态的隐藏属性: $CachedCurrentState$CachedData。 将 的类型 $CachedCurrentState 设置为 Tailspin,与 方法的 类和返回类型 Get() 相同。 将 类型 $CachedData 设置为 PSCustomObject。 为两个属性添加 关键字前缀 hidden 。 不要为两者指定 DscProperty 属性。

hidden [Tailspin] $CachedCurrentState
hidden [PSCustomObject] $CachedData

这些隐藏属性将用在稍后定义的 和 Set() 方法中Get()

查看模块文件

此时, ExampleResources.psm1 应定义:

  • 具有属性 $ConfigurationScope$Ensure$UpdateAutomatically和 的 Tailspin$UpdateFrequency
  • TailspinScope 枚举,其值为 MachineUser
  • TailspinEnsure 枚举,其值为 PresentAbsent
  • Test()Set() 方法的Get()最小实现。
[DscResource()]
class Tailspin {
    [DscProperty(Key)] [TailspinScope]
    $ConfigurationScope

    [DscProperty()] [TailspinEnsure]
    $Ensure = [TailspinEnsure]::Present

    [DscProperty(Mandatory)] [bool]
    $UpdateAutomatically

    [DscProperty()] [int] [ValidateRange(1,90)]
    $UpdateFrequency

    hidden [Tailspin] $CachedCurrentState
    hidden [PSCustomObject] $CachedData

    [Tailspin] Get() {
        $CurrentState = [Tailspin]::new()
        return $CurrentState
    }

    [bool] Test() {
        $InDesiredState = $true
        return $InDesiredState
    }

    [void] Set() {}
}

enum TailspinScope {
    Machine
    User
}

enum TailspinEnsure {
    Absent
    Present
}

现在,DSC 资源满足要求,可以使用 Get-DscResource 来查看它。 在 VS Code 中,打开新的 PowerShell 终端。

. ./Helpers.ps1
Get-DscResource -Name Tailspin -Module ExampleResources | Format-List
Get-DscResource -Name Tailspin -Module ExampleResources -Syntax
ImplementationDetail : ClassBased
ResourceType         : Tailspin
Name                 : Tailspin
FriendlyName         :
Module               : ExampleResources
ModuleName           : ExampleResources
Version              : 0.0.1
Path                 : C:\code\dsc\ExampleResources\ExampleResources.psd1
ParentPath           : C:\code\dsc\ExampleResources
ImplementedAs        : PowerShell
CompanyName          : Unknown
Properties           : {ConfigurationScope, UpdateAutomatically, DependsOn, Ensure…}

Tailspin [String] #ResourceName
{
    ConfigurationScope = [string]{ Machine | User }
    UpdateAutomatically = [bool]
    [DependsOn = [string[]]]
    [Ensure = [string]{ Absent | Present }]
    [PsDscRunAsCredential = [PSCredential]]
    [UpdateFrequency = [Int32]]
}

4 - 实现 DSC 资源方法

DSC 资源的方法定义如何检索 DSC 资源的当前状态,根据所需状态对其进行验证,并强制实施所需状态。

Get 方法

方法 Get() 检索 DSC 资源的当前状态。 它用于手动检查 DSC 资源,并由 Test() 方法调用。

方法 Get() 没有参数,并返回 类的实例作为其输出。 Tailspin对于 DSC 资源,最小实现如下所示:

[Tailspin] Get() {
    $CurrentState = [Tailspin]::new()
    return $CurrentState
}

此实现执行的唯一操作是创建 Tailspin 类的实例并返回它。 可以使用 调用 方法 Invoke-DscResource 以查看此行为。

Invoke-DscResource -Name Tailspin -Module ExampleResources -Method Get -Property @{
    ConfigurationScope  = 'User'
    UpdateAutomatically = $true
}
ConfigurationScope  Ensure UpdateAutomatically UpdateFrequency
------------------  ------ ------------------- ---------------
           Machine Present               False               0

返回对象的属性全部设置为其默认值。 的值 $ConfigurationScope 应始终为用户提供的值。 若要使 Get() 该方法有用,它必须返回 DSC 资源的实际状态。

[Tailspin] Get() {
    $CurrentState = [Tailspin]::new()

    $CurrentState.ConfigurationScope = $this.ConfigurationScope

    $this.CachedCurrentState = $CurrentState

    return $CurrentState
}

变量 $this 引用 DSC 资源的工作实例。 现在,如果再次使用 Invoke-DscResource$ConfigurationScope 具有正确的值。

Invoke-DscResource -Name Tailspin -Module ExampleResources -Method Get -Property @{
    ConfigurationScope  = 'User'
    UpdateAutomatically = $true
}
ConfigurationScope  Ensure UpdateAutomatically UpdateFrequency
------------------  ------ ------------------- ---------------
              User Present               False               0

接下来,DSC 资源需要确定配置文件是否存在。 如果是, $Ensure 应为 Present。 否则, $Ensure 应为 Absent

TSToy 的配置文件的位置取决于操作系统和配置范围:

  • 对于 Windows 计算机:
    • 配置文件 Machine%PROGRAMDATA%\TailSpinToys\tstoy\tstoy.config.json
    • 配置文件 User%APPDATA%\TailSpinToys\tstoy\tstoy.config.json
  • 对于 Linux 计算机:
    • 配置文件 Machine/etc/xdg/TailSpinToys/tstoy/tstoy.config.json
    • 配置文件 User~/.config/TailSpinToys/tstoy/tstoy.config.json
  • 对于 macOS 计算机:
    • 配置文件 Machine/Library/Preferences/TailSpinToys/tstoy/tstoy.config.json
    • 配置文件 User~/Library/Preferences/TailSpinToys/tstoy/tstoy.config.json

若要处理这些路径,需要创建帮助程序方法 GetConfigurationFile()

[string] GetConfigurationFile() {
    $FilePaths = @{
        Linux = @{
            Machine   = '/etc/xdg/TailSpinToys/tstoy/tstoy.config.json'
            User      = '~/.config/TailSpinToys/tstoy/tstoy.config.json'
        }
        MacOS = @{
            Machine   = '/Library/Preferences/TailSpinToys/tstoy/tstoy.config.json'
            User      = '~/Library/Preferences/TailSpinToys/tstoy/tstoy.config.json'
        }
        Windows = @{
            Machine = "$env:ProgramData\TailSpinToys\tstoy\tstoy.config.json"
            User    = "$env:APPDATA\TailSpinToys\tstoy\tstoy.config.json"
        }
    }

    $Scope = $this.ConfigurationScope.ToString()

    if ($Global:PSVersionTable.PSVersion.Major -lt 6 -or $Global:IsWindows) {
        return $FilePaths.Windows.$Scope
    } elseif ($Global:IsLinux) {
        return $FilePaths.Linux.$Scope
    } else {
        return $FilePaths.MacOS.$Scope
    }
}

若要测试此新方法,请执行 using 语句,将 ExampleResources 模块的类和枚举加载到当前会话中。

using module ./ExampleResources.psd1
$Example = [Tailspin]::new()
$Example
$Example.GetConfigurationFile()
$Example.ConfigurationScope = 'User'
$Example.GetConfigurationFile()
Ensure  ConfigurationScope UpdateAutomatically UpdateFrequency
------- ------------------ ------------------- ---------------
Present            Machine               False               0

C:\ProgramData\TailSpinToys\tstoy\tstoy.config.json

C:\Users\mikey\AppData\Roaming\TailSpinToys\tstoy\tstoy.config.json

在 VS Code 中打开 Helpers.ps1 。 将配置文件的路径复制并粘贴到脚本中,并将其 $TSToyMachinePath 分配给 和 $TSToyUserPath。 该文件应如下所示:

$env:PSModulePath += "<separator>$pwd"
$TSToyMachinePath = '<machine configuration file path>'
$TSToyUserPath = '<user configuration file path>'

退出 VS Code 中的终端并打开新的终端。 点源 Helpers.ps1

. ./Helpers.ps1

现在可以编写方法的 Get() 其余部分。

[Tailspin] Get() {
    $CurrentState = [Tailspin]::new()

    $CurrentState.ConfigurationScope = $this.ConfigurationScope

    $FilePath = $this.GetConfigurationFile()

    if (!(Test-Path -Path $FilePath)) {
        $CurrentState.Ensure = [TailspinEnsure]::Absent
        return $CurrentState
    }

    $Data = Get-Content -Raw -Path $FilePath |
        ConvertFrom-Json -ErrorAction Stop

    $this.CachedData = $Data

    if ($null -ne $Data.Updates.Automatic) {
        $CurrentState.UpdateAutomatically = $Data.Updates.Automatic
    }

    if ($null -ne $Data.Updates.CheckFrequency) {
        $CurrentState.UpdateFrequency = $Data.Updates.CheckFrequency
    }

    $this.CachedCurrentState = $CurrentState

    return $CurrentState
}

设置 $ConfigurationScope 并确定配置文件的路径后, 方法检查文件是否存在。 如果不存在,只需将 设置为 $EnsureAbsent 并返回结果。

如果文件确实存在, 方法需要从 JSON 转换内容,以创建配置的当前状态。 接下来, 方法在将键分配给当前状态的属性之前检查它们是否具有任何值。 如果未指定它们,DSC 资源必须将其视为未设置且处于默认状态。

此时,DSC 资源缓存数据。 缓存数据可以在开发期间检查数据,并在实现 Set() 方法时非常有用。

可以在本地验证此行为。

$GetParameters = @{
    Name     = 'Tailspin'
    Module   = 'ExampleResources'
    Method   = 'Get'
    Property = @{
        ConfigurationScope = 'User'
    }
}

Invoke-DscResource @GetParameters
New-Item -Path $UserPath -Force
Invoke-DscResource @GetParameters
ConfigurationScope Ensure UpdateAutomatically UpdateFrequency
------------------ ------ ------------------- ---------------
              User Absent               False               0

    Directory: C:\Users\mikey\AppData\Roaming\TailSpinToys\tstoy

Mode                 LastWriteTime         Length Name
----                 -------------         ------ ----
-a---           9/15/2022  3:43 PM              0 tstoy.config.json

ConfigurationScope  Ensure UpdateAutomatically UpdateFrequency
------------------  ------ ------------------- ---------------
              User Present               False               0

User VS Code 中打开范围配置文件。

code $UserPath

将此 JSON 配置复制到 文件中并保存。

{
    "unmanaged_key": true,
    "updates": {
        "automatic": true,
        "checkFrequency": 30
    }
}

再次调用 Invoke-DscResource 并查看结果中反映的值。

Invoke-DscResource @GetParameters
ConfigurationScope  Ensure UpdateAutomatically UpdateFrequency
------------------  ------ ------------------- ---------------
              User Present                True              30

方法 Get() 现在返回有关 DSC 资源的当前状态的准确信息。

Test 方法

实现 方法后 Get() ,可以验证当前状态是否符合所需状态。

最小 Test() 实现方法始终返回 $true

[bool] Test() {
    return $true
}

你可以通过运行 Invoke-DscResource 来进行验证。

$SharedParameters = @{
    Name     = 'Tailspin'
    Module   = 'ExampleResources'
    Property = @{
        ConfigurationScope = 'User'
        UpdateAutomatically = $false
    }
}

Invoke-DscResource -Method Get @SharedParameters
Invoke-DscResource -Method Test @SharedParameters
ConfigurationScope  Ensure UpdateAutomatically UpdateFrequency
------------------  ------ ------------------- ---------------
              User Present                True              30

InDesiredState
--------------
          True

需要使 Test() 方法准确反映 DSC 资源是否处于所需状态。 方法 Test() 应始终调用 方法, Get() 使当前状态与所需状态进行比较。 然后检查属性 $Ensure 是否正确。 如果不是,请立即返回 $false 。 如果 $Ensure 属性处于所需状态,则无需进一步检查。

[bool] Test() {
    $CurrentState = $this.Get()

    if ($CurrentState.Ensure -ne $this.Ensure) {
        return $false
    }

    return $true
}

现在可以验证更新后的行为。

$TestParameters = @{
    Name     = 'Tailspin'
    Module   = 'ExampleResources'
    Property = @{
        ConfigurationScope  = 'User'
        UpdateAutomatically = $false
        Ensure              = 'Absent'
    }
}

Invoke-DscResource -Method Test @TestParameters

$TestParameters.Property.Ensure = 'Present'

Invoke-DscResource -Method Test @TestParameters
InDesiredState
--------------
         False

InDesiredState
--------------
          True

接下来,检查 的值 $Ensure 是否为 Absent。 如果配置文件不存在且不应存在,则没有理由检查其余属性。

[bool] Test() {
    $CurrentState = $this.Get()

    if ($CurrentState.Ensure -ne $this.Ensure) {
        return $false
    }

    if ($CurrentState.Ensure -eq [TailspinEnsure]::Absent) {
        return $true
    }

    return $true
}

接下来, 方法需要比较管理 TSToy 更新行为的属性的当前状态。 首先,检查属性是否 $UpdateAutomatically 处于正确的状态。 如果不是,则返回 $false

[bool] Test() {
    $CurrentState = $this.Get()

    if ($CurrentState.Ensure -ne $this.Ensure) {
        return $false
    }

    if ($CurrentState.Ensure -eq [TailspinEnsure]::Absent) {
        return $true
    }

    if ($CurrentState.UpdateAutomatically -ne $this.UpdateAutomatically) {
        return $false
    }

    return $true
}

若要比较 $UpdateFrequency,需要确定用户是否指定了 值。 由于 $UpdateFrequency 初始化为 0 ,并且 属性的 ValidateRange 属性指定它必须介于 和 90之间1,我们知道 值 指示0未指定属性。

有了这些信息, Test() 方法应:

  1. 如果用户未指定,则返回$true$UpdateFrequency
  2. 如果用户指定$UpdateFrequency了 ,并且系统的值不等于用户指定的值,则返回$false
  3. 如果未满足上述两个条件,则返回$true
[bool] Test() {
    $CurrentState = $this.Get()

    if ($CurrentState.Ensure -ne $this.Ensure) {
        return $false
    }

    if ($CurrentState.Ensure -eq [TailspinEnsure]::Absent) {
        return $true
    }

    if ($CurrentState.UpdateAutomatically -ne $this.UpdateAutomatically) {
        return $false
    }

    if ($this.UpdateFrequency -eq 0) {
        return $true
    }

    if ($CurrentState.UpdateFrequency -ne $this.UpdateFrequency) {
        return $false
    }

    return $true
}

现在, Test() 方法使用以下操作顺序:

  1. 检索 TSToy 配置的当前状态。
  2. 如果配置在不应存在时存在,则返回 ;如果配置应不存在,则返回 $false
  3. 如果配置不存在且不应存在,则返回 $true
  4. 如果配置的自动更新设置与所需设置不匹配,则返回 $false
  5. 如果用户未为更新频率设置指定值,则返回 $true
  6. 如果用户为更新频率设置指定的值与配置的设置不匹配,则返回 $false
  7. 如果未满足上述任何条件,则返回 $true

可以在本地验证 Test() 方法:

$SharedParameters = @{
    Name     = 'Tailspin'
    Module   = 'ExampleResources'
    Property = @{
        ConfigurationScope  = 'User'
        Ensure              = 'Present'
        UpdateAutomatically = $false
    }
}

Invoke-DscResource -Method Get @SharedParameters

Invoke-DscResource -Method Test @SharedParameters

$SharedParameters.Property.UpdateAutomatically = $true
Invoke-DscResource -Method Test @SharedParameters

$SharedParameters.Property.UpdateFrequency = 1
Invoke-DscResource -Method Test @SharedParameters
ConfigurationScope  Ensure UpdateAutomatically UpdateFrequency
------------------  ------ ------------------- ---------------
              User Present                True              30

InDesiredState
--------------
         False

InDesiredState
--------------
          True

InDesiredState
--------------
         False

使用此代码, Test() 方法能够准确地确定配置文件是否处于所需状态。

Set 方法

Get()既然 和 Test() 方法可靠工作,可以定义 Set() 方法以实际强制实施所需状态。

在最小实现中 Set() , 方法不执行任何作用。

[void] Set() {}

首先, Set() 需要确定是否需要创建、更新或删除 DSC 资源。

[void] Set() {
    if ($this.Test()) {
            return
    }

    $CurrentState = $this.CachedCurrentState

    $IsAbsent = $CurrentState.Ensure -eq [TailspinEnsure]::Absent
    $ShouldBeAbsent = $this.Ensure -eq [TailspinEnsure]::Absent

    if ($IsAbsent) {
        # Create
    } elseif ($ShouldBeAbsent) {
        # Remove
    } else {
        # Update
    }
}

Set() 首先调用 Test() 方法以确定是否需要执行任何操作。 某些工具(例如 Azure Automanage 的计算机配置功能)确保 Set() 方法仅在 方法之后 Test() 调用。 但是,使用 Invoke-DscResource cmdlet 时没有这样的保证。

Test()由于 方法调用 Get()缓存当前状态的 ,因此 DSC 资源可以访问缓存的当前状态,而无需再次调用 Get() 方法。

接下来,DSC 资源需要区分配置文件的创建、删除和更新行为。 如果配置文件不存在,我们知道应该创建它。 如果配置文件确实存在且不应存在,则我们知道需要将其删除。 如果配置文件确实存在并且应该存在,我们知道需要更新它。

创建三个新方法来处理这些操作,并根据需要在 方法中 Set() 调用它们。 所有三个的返回类型都应为 void

[void] Set() {
    if ($this.Test()) {
            return
    }

    $CurrentState = $this.CachedCurrentState

    $IsAbsent = $CurrentState.Ensure -eq [TailspinEnsure]::Absent
    $ShouldBeAbsent = $this.Ensure -eq [TailspinEnsure]::Absent

    if ($IsAbsent) {
        $this.Create()
    } elseif ($ShouldBeAbsent) {
        $this.Remove()
    } else {
        $this.Update()
    }
}

[void] Create() {}
[void] Remove() {}
[void] Update() {}

此外,创建一个名为 ToConfigJson()的新方法。 其返回类型应为 字符串。 此方法将 DSC 资源转换为配置文件所需的 JSON。 可以从以下最小实现开始:

[string] ToConfigJson() {
    $config = @{}

    return ($config | ConvertTo-Json)
}

ToConfigJson 方法

最小实现将空 JSON 对象作为字符串返回。 若要使其有用,它需要返回 TSToy 配置文件中设置的实际 JSON 表示形式。

首先,使用强制自动更新设置预填充 $config 哈希表, updates 方法是将键的值添加为 哈希表。 哈希表应具有 automatic 键。 将类的 $UpdateAutomatically 属性的值分配给 automatic 键。

[string] ToConfigJson() {
    $config = @{
        updates = @{
            automatic = $this.UpdateAutomatically
        }
    }

    return ($config | ConvertTo-Json)
}

此代码将 TSToy 设置的 DSC 资源表示形式转换为 TSToy 的配置文件所需的结构。

接下来,方法需要检查 类是否缓存了现有配置文件中的数据。 缓存的数据允许 DSC 资源管理定义的设置,而不会覆盖或删除非托管设置。

[string] ToConfigJson() {
    $config = @{
        updates = @{
            automatic = $this.UpdateAutomatically
        }
    }

    if ($this.CachedData) {
        # Copy unmanaged settings without changing the cached values
        $this.CachedData |
            Get-Member -MemberType NoteProperty |
            Where-Object -Property Name -NE -Value 'updates' |
            ForEach-Object -Process {
                $setting = $_.Name
                $config.$setting = $this.CachedData.$setting
            }

        # Add the checkFrequency to the hashtable if it is set in the cache
        if ($frequency = $this.CachedData.updates.checkFrequency) {
            $config.updates.checkFrequency = $frequency
        }
    }

    # If the user specified an UpdateFrequency, use that value
    if ($this.UpdateFrequency -ne 0) {
        $config.updates.checkFrequency = $this.UpdateFrequency
    }

    return ($config | ConvertTo-Json)
}

如果类缓存了现有配置中的设置,则它:

  1. 检查缓存数据的属性,查找 DSC 资源未管理的任何属性。 如果找到任何属性,方法会将这些非托管属性插入到 $config 哈希表中。

    由于 DSC 资源仅管理更新设置,因此插入除 以外的 updates 每个设置。

  2. 检查是否 checkFrequency 设置了 中的 updates 设置。 如果设置了该值,方法会将此值插入到 $config 哈希表中。

    如果用户未指定属性, $UpdateFrequency 则此操作允许 DSC 资源忽略该属性。

  3. 最后,方法需要检查用户是否指定了 属性, $UpdateFrequency 并在指定时将其插入哈希 $config 表中。

使用此代码,方法 ToConfigJson()

  1. 返回 TSToy 应用程序在其配置文件中所需的所需状态的准确 JSON 表示形式
  2. 遵循 DSC 资源未显式管理的任何 TSToy 设置
  3. 如果用户未指定,则遵循 TSToy 更新频率的现有值,包括在配置文件中保留它未定义

若要测试此新方法,请关闭 VS Code 终端并打开一个新方法。 using执行 语句将 ExampleResources 模块的类和枚举加载到当前会话中,并点源脚本helpers.ps1

using module ./ExampleResources.psd1
. ./Helpers.ps1
$Example = [Tailspin]::new()
Get-Content -Path $UserPath
$Example.ConfigurationScope = 'User'
$Example.ToConfigJson()

Get()在调用 方法之前,ToJsonConfig 方法输出中的唯一值是 属性的$UpdateAutomatically转换值。

{
    "unmanaged_key": true,
    "updates": {
        "automatic": true,
        "checkFrequency": 30
    }
}

{
  "updates": {
    "automatic": false
  }
}
$Example.Get()
$Example.ToConfigJson()

调用 Get()后,输出包括非托管顶级键 unmanaged_key。 它还包括 配置文件中的现有设置, $UpdateFrequency 因为它未在 DSC 资源上显式设置。

 Ensure ConfigurationScope UpdateAutomatically UpdateFrequency
 ------ ------------------ ------------------- ---------------
Present               User                True              30

{
  "unmanaged_key": true,
  "updates": {
    "checkFrequency": 30,
    "automatic": false
  }
}
$Example.UpdateFrequency = 7
$Example.ToConfigJson()

设置 后 $UpdateFrequency ,输出将反映指定的值。

{
  "unmanaged_key": true,
  "updates": {
    "checkFrequency": 7,
    "automatic": false
  }
}

Create 方法

若要实现 Create() 方法,我们需要将 DSC 资源的用户指定属性转换为 TSToy 在其配置文件中预期的 JSON,并将其写入该文件。

[void] Create() {
    $ErrorActionPreference = 'Stop'

    $Json = $this.ToConfigJson()

    $FilePath   = $this.GetConfigurationFile()
    $FolderPath = Split-Path -Path $FilePath

    if (!(Test-Path -Path $FolderPath)) {
        New-Item -Path $FolderPath -ItemType Directory -Force
    }

    Set-Content -Path $FilePath -Value $Json -Encoding utf8 -Force
}

方法使用 ToConfigJson() 方法获取配置文件的 JSON。 它会检查配置文件的文件夹是否存在,并在必要时创建它。 最后,它会创建配置文件,并将 JSON 写入其中。

Remove 方法

方法 Remove() 具有最简单的行为。 如果配置文件存在,请将其删除。

[void] Remove() {
    Remove-Item -Path $this.GetConfigurationFile() -Force -ErrorAction Stop
}

Update 方法

方法 Update() 的实现类似于 Create 方法。 它需要将 DSC 资源的用户指定属性转换为 TSToy 在其配置文件中预期的 JSON,并替换该文件中的设置。

[void] Update() {
    $ErrorActionPreference = 'Stop'

    $Json = $this.ToConfigJson()
    $FilePath   = $this.GetConfigurationFile()

    Set-Content -Path $FilePath -Value $Json -Encoding utf8 -Force
}

5 - 手动测试 DSC 资源

完全实现 DSC 资源后,现在可以测试其行为。

在测试之前,请关闭 VS Code 终端并打开一个新终端。 点源 Helpers.ps1 脚本。 对于每个测试方案,创建 $DesiredState 包含共享参数的哈希表,并按以下顺序调用方法:

  1. Get(),用于检索 DSC 资源的初始状态
  2. Test(),查看 DSC 资源是否认为它处于所需状态
  3. Set(),以强制实施所需状态
  4. Test(),查看 DSC 资源是否认为它已正确设置
  5. Get(),确认 DSC 资源的最终状态

场景:TSToy 不应在用户范围内自动更新

在这种情况下,需要将用户范围中的现有配置配置为不自动更新。 所有其他设置应保持不变。

. ./Helpers.ps1

$DesiredState = @{
    Name     = 'Tailspin'
    Module   = 'ExampleResources'
    Property = @{
        ConfigurationScope  = 'User'
        UpdateAutomatically = $false
        Ensure              = 'Present'
    }
}

Get-Content -Path $UserPath

Invoke-DscResource @DesiredState -Method Get
Invoke-DscResource @DesiredState -Method Test
Invoke-DscResource @DesiredState -Method Set
Invoke-DscResource @DesiredState -Method Test
Invoke-DscResource @DesiredState -Method Get

Get-Content -Path $UserPath
{
    "unmanaged_key": true,
    "updates": {
        "automatic": true,
        "checkFrequency": 30
    }
}

ConfigurationScope  Ensure UpdateAutomatically UpdateFrequency
------------------  ------ ------------------- ---------------
              User Present                True              30

InDesiredState
--------------
         False

RebootRequired
--------------
         False

InDesiredState
--------------
          True

ConfigurationScope  Ensure UpdateAutomatically UpdateFrequency
------------------  ------ ------------------- ---------------
              User Present               False              30

{
  "unmanaged_key": true,
  "updates": {
    "checkFrequency": 30,
    "automatic": false
  }
}

场景:Tailspin 应根据用户范围中的任何计划自动更新

在这种情况下,需要将用户范围中的现有配置配置为自动更新。 所有其他设置应保持不变。

. ./Helpers.ps1

$DesiredState = @{
    Name     = 'Tailspin'
    Module   = 'ExampleResources'
    Property = @{
        ConfigurationScope  = 'User'
        UpdateAutomatically = $true
        Ensure              = 'Present'
    }
}

Get-Content -Path $UserPath

Invoke-DscResource @DesiredState -Method Get
Invoke-DscResource @DesiredState -Method Test
Invoke-DscResource @DesiredState -Method Set
Invoke-DscResource @DesiredState -Method Test
Invoke-DscResource @DesiredState -Method Get

Get-Content -Path $UserPath
{
  "unmanaged_key": true,
  "updates": {
    "checkFrequency": 30,
    "automatic": false
  }
}

ConfigurationScope  Ensure UpdateAutomatically UpdateFrequency
------------------  ------ ------------------- ---------------
              User Present               False              30

InDesiredState
--------------
         False

RebootRequired
--------------
         False

InDesiredState
--------------
          True

ConfigurationScope  Ensure UpdateAutomatically UpdateFrequency
------------------  ------ ------------------- ---------------
              User Present                True              30

{
  "unmanaged_key": true,
  "updates": {
    "checkFrequency": 30,
    "automatic": true
  }
}

场景:TSToy 应在用户范围内每天自动更新

在此方案中,需要将用户范围中的现有配置配置为每天自动更新。 所有其他设置应保持不变。

. ./Helpers.ps1

$DesiredState = @{
    Name     = 'Tailspin'
    Module   = 'ExampleResources'
    Property = @{
        ConfigurationScope  = 'User'
        UpdateAutomatically = $true
        UpdateFrequency     = 1
        Ensure              = 'Present'
    }
}

Get-Content -Path $UserPath

Invoke-DscResource @DesiredState -Method Get
Invoke-DscResource @DesiredState -Method Test
Invoke-DscResource @DesiredState -Method Set
Invoke-DscResource @DesiredState -Method Test
Invoke-DscResource @DesiredState -Method Get

Get-Content -Path $UserPath
{
  "unmanaged_key": true,
  "updates": {
    "checkFrequency": 30,
    "automatic": true
  }
}

ConfigurationScope  Ensure UpdateAutomatically UpdateFrequency
------------------  ------ ------------------- ---------------
              User Present                True              30

InDesiredState
--------------
         False

RebootRequired
--------------
         False

InDesiredState
--------------
          True

ConfigurationScope  Ensure UpdateAutomatically UpdateFrequency
------------------  ------ ------------------- ---------------
              User Present                True               1

{
  "unmanaged_key": true,
  "updates": {
    "automatic": true,
    "checkFrequency": 1
  }
}

场景:TSToy 不应具有用户范围配置

在此方案中,用户范围内 TSToy 的配置文件不应存在。 如果存在,DSC 资源应删除该文件。

. ./Helpers.ps1

$DesiredState = @{
    Name     = 'Tailspin'
    Module   = 'ExampleResources'
    Property = @{
        ConfigurationScope  = 'User'
        UpdateAutomatically = $true
        Ensure              = 'Absent'
    }
}

Get-Content -Path $UserPath

Invoke-DscResource @DesiredState -Method Get
Invoke-DscResource @DesiredState -Method Test
Invoke-DscResource @DesiredState -Method Set
Invoke-DscResource @DesiredState -Method Test
Invoke-DscResource @DesiredState -Method Get

Test-Path -Path $UserPath
{
  "unmanaged_key": true,
  "updates": {
    "checkFrequency": 30,
    "automatic": true
  }
}

ConfigurationScope  Ensure UpdateAutomatically UpdateFrequency
------------------  ------ ------------------- ---------------
              User Present                True              30

InDesiredState
--------------
         False

RebootRequired
--------------
         False

InDesiredState
--------------
          True

ConfigurationScope Ensure UpdateAutomatically UpdateFrequency
------------------ ------ ------------------- ---------------
              User Absent               False               0

False

场景:TSToy 应在计算机范围内每周自动更新

在此方案中,计算机范围中没有定义的配置。 需要将计算机范围配置为每天自动更新。 DSC 资源应根据需要创建文件和任何父文件夹。

. ./Helpers.ps1

$DesiredState = @{
    Name     = 'Tailspin'
    Module   = 'ExampleResources'
    Property = @{
        ConfigurationScope  = 'Machine'
        UpdateAutomatically = $true
        Ensure              = 'Present'
    }
}

Test-Path -Path $MachinePath, (Split-Path -Path $MachinePath)

Invoke-DscResource @DesiredState -Method Get
Invoke-DscResource @DesiredState -Method Test
Invoke-DscResource @DesiredState -Method Set
Invoke-DscResource @DesiredState -Method Test
Invoke-DscResource @DesiredState -Method Get

Get-Content -Path $MachinePath
False
False

ConfigurationScope Ensure UpdateAutomatically UpdateFrequency
------------------ ------ ------------------- ---------------
           Machine Absent               False               0

InDesiredState
--------------
         False

RebootRequired
--------------
         False

InDesiredState
--------------
          True

ConfigurationScope  Ensure UpdateAutomatically UpdateFrequency
------------------  ------ ------------------- ---------------
           Machine Present                True               0

{
  "updates": {
    "automatic": true
  }
}

审阅

本教程介绍以下操作:

  1. 搭建了 PowerShell 模块并实现了基于类的 Tailspin DSC 资源
  2. 定义了 DSC 资源的属性,以通过验证这些属性来管理计算机和用户范围内的 TSToy 应用程序的更新行为
  3. $Ensure$ConfigurationScope 属性实现了枚举
  4. GetConfigurationFile()实现了帮助程序方法,以跨平台可靠地发现 TSToy 的应用程序配置在计算机和用户范围内的位置
  5. Get()实现了 方法以检索 DSC 资源的当前状态,并缓存它以便在 和 Set() 方法中使用Test()
  6. 实现了 方法, Test() 以根据所需状态验证特定范围内 TSToy 的更新行为的当前状态
  7. 实现了 ToConfigJson 方法,以将 DSC 资源的所需状态转换为 TSToy 应用程序为其配置文件所需的 JSON 对象,并遵循非托管设置
  8. Set()实现了 方法和 CreateRemoveUpdate 帮助程序方法,以幂等方式在特定范围内强制实施 TSToy 更新行为的所需状态,确保 DSC 资源不会产生不良的副作用
  9. 手动测试 DSC 资源的常见使用方案

在实现结束时,模块定义如下所示:

[DscResource()]
class Tailspin {
    [DscProperty(Key)] [TailspinScope]
    $ConfigurationScope

    [DscProperty()] [TailspinEnsure]
    $Ensure = [TailspinEnsure]::Present

    [DscProperty(Mandatory)] [bool]
    $UpdateAutomatically

    [DscProperty()] [int] [ValidateRange(1, 90)]
    $UpdateFrequency

    hidden [Tailspin] $CachedCurrentState
    hidden [PSCustomObject] $CachedData

    [Tailspin] Get() {
        $CurrentState = [Tailspin]::new()

        $CurrentState.ConfigurationScope = $this.ConfigurationScope

        $FilePath = $this.GetConfigurationFile()

        if (!(Test-Path -Path $FilePath)) {
            $CurrentState.Ensure = [TailspinEnsure]::Absent

            $this.CachedCurrentState = $CurrentState

            return $CurrentState
        }

        $CurrentState.Ensure = [TailspinEnsure]::Present

        $Data = Get-Content -Raw -Path $FilePath |
            ConvertFrom-Json -ErrorAction Stop

        $this.CachedData = $Data

        if ($null -ne $Data.Updates.Automatic) {
            $CurrentState.UpdateAutomatically = $Data.Updates.Automatic
        }

        if ($null -ne $Data.Updates.CheckFrequency) {
            $CurrentState.UpdateFrequency = $Data.Updates.CheckFrequency
        }

        $this.CachedCurrentState = $CurrentState

        return $CurrentState
    }

    [bool] Test() {
        $CurrentState = $this.Get()

        if ($CurrentState.Ensure -ne $this.Ensure) {
            return $false
        }

        if ($CurrentState.UpdateAutomatically -ne $this.UpdateAutomatically) {
            return $false
        }

        if ($this.UpdateFrequency -eq 0) {
            return $true
        }

        if ($CurrentState.UpdateFrequency -ne $this.UpdateFrequency) {
            return $false
        }

        return $true
    }

    [void] Set() {
        if ($this.Test()) {
            return
        }

        $CurrentState = $this.CachedCurrentState

        $IsAbsent = $CurrentState.Ensure -eq [TailspinEnsure]::Absent
        $ShouldBeAbsent = $this.Ensure -eq [TailspinEnsure]::Absent

        if ($IsAbsent) {
            $this.Create()
        } elseif ($ShouldBeAbsent) {
            $this.Remove()
        } else {
            $this.Update()
        }
    }

    [string] GetConfigurationFile() {
        $FilePaths = @{
            Linux   = @{
                Machine = '/etc/xdg/TailSpinToys/tstoy/tstoy.config.json'
                User    = '~/.config/TailSpinToys/tstoy/tstoy.config.json'
            }
            MacOS   = @{
                Machine = '/Library/Preferences/TailSpinToys/tstoy/tstoy.config.json'
                User    = '~/Library/Preferences/TailSpinToys/tstoy/tstoy.config.json'
            }
            Windows = @{
                Machine = "$env:ProgramData\TailSpinToys\tstoy\tstoy.config.json"
                User    = "$env:APPDATA\TailSpinToys\tstoy\tstoy.config.json"
            }
        }

        $Scope = $this.ConfigurationScope.ToString()

        if ($Global:PSVersionTable.PSVersion.Major -lt 6 -or $Global:IsWindows) {
            return $FilePaths.Windows.$Scope
        }
        elseif ($Global:IsLinux) {
            return $FilePaths.Linux.$Scope
        }
        else {
            return $FilePaths.MacOS.$Scope
        }
    }

    [void] Create() {
        $ErrorActionPreference = 'Stop'

        $Json = $this.ToConfigJson()

        $FilePath   = $this.GetConfigurationFile()
        $FolderPath = Split-Path -Path $FilePath

        if (!(Test-Path -Path $FolderPath)) {
            New-Item -Path $FolderPath -ItemType Directory -Force
        }

        Set-Content -Path $FilePath -Value $Json -Encoding utf8 -Force
    }

    [void] Remove() {
        Remove-Item -Path $this.GetConfigurationFile() -Force -ErrorAction Stop
    }

    [void] Update() {
        $ErrorActionPreference = 'Stop'

        $Json = $this.ToConfigJson()
        $FilePath   = $this.GetConfigurationFile()

        Set-Content -Path $FilePath -Value $Json -Encoding utf8 -Force
    }

    [string] ToConfigJson() {
        $config = @{
            updates = @{
                automatic = $this.UpdateAutomatically
            }
        }

        if ($this.CachedData) {
            $this.CachedData |
                Get-Member -MemberType NoteProperty |
                Where-Object -Property Name -NE -Value 'updates' |
                ForEach-Object -Process {
                    $setting = $_.Name
                    $config.$setting = $this.CachedData.$setting
                }

            if ($frequency = $this.CachedData.updates.CheckFrequency) {
                $config.updates.checkFrequency = $frequency
            }
        }

        if ($this.UpdateFrequency -ne 0) {
            $config.updates.checkFrequency = $this.UpdateFrequency
        }

        return ($config | ConvertTo-Json)
    }
}

enum TailspinScope {
    Machine
    User
}

enum TailspinEnsure {
    Absent
    Present
}

清除

如果不打算继续使用此模块,请删除该 ExampleResources 文件夹和其中的文件。

后续步骤

  1. 阅读 基于类的 DSC 资源,了解它们的工作原理,并考虑本教程中 DSC 资源以这种方式实现的原因。
  2. 考虑如何改进此 DSC 资源。 是否有任何边缘案例或功能无法处理? 更新实现以处理它们。