此文章由机器翻译。

Windows PowerShell

创作理想状态配置自定义资源

Ritesh Modi

所需状态配置 (DSC) 是一个新的配置管理和部署平台从微软。它建立在共同信息模型 (CIM),管理 (WSMAN) 的行业标准的 Web 服务,并且是对 Windows PowerShell 的扩展。

您可以以声明方式编写在任何 Windows PowerShell 控制台内的 DSC 脚本生成 DSC 对象,在目标服务器上执行它们。DSC 允许您配置、 监测和确保使用这些配置文件的服务器的符合性。

配置写就像定义策略为目标服务器。该策略中包含一组资源及其所需的状态,并且那些应用于目标服务器。DSC 可确保每个服务器遵循的策略。了解更多在 DSC bit.ly/1iDeRcF

资源是基本的构造块的 DSC。他们是 DSC 基础结构最颗粒的可分配、 可重复使用、 可共享的组件。他们还提供最低级别的控制内 DSC 和用于创作 DSC 文件配置基础结构。DSC 资源可以执行任何可能使用 Windows PowerShell,如配置和管理 IIS,Windows 远程管理 (WinRM)、 注册表、 Windows 功能和服务。

什么好这几天是一种技术,如果它不允许延长?DSC 遵循开闭原则。它是对扩展开放,但对变更封闭。它为创作新的资源提供了必要的延伸,挂钩。

在这篇文章,我将展示你如何开发 DSC 自定义资源和在 DSC 配置示例中使用它。自定义资源被命名为 TrustedHosts。此资源将管理 WinRM 受信任的主机配置 — — 添加或删除到 WinRM TrustedHosts 属性的计算机名称。

想要使用 Windows PowerShell 远程处理通信的工作组服务器 (不是任何域的一部分) 将需要额外的 WinRM 配置。具体来说,我将添加 WinRM TrustedHosts 列表,以允许从那些机器的远程处理的客户端计算机的名称。

此资源将负责管理 TrustedHosts 列表中的单个计算机名称。但是,如果您需要多个计算机名称,您可以添加使用此资源配置文档中的多个资源部分。您还可以修改伴随着的资源。

您可以通过执行 Get 项目命令和使用 WSMAN 提供程序查看受信任的主机配置:

Get-Item WSMan:\localhost\client\TrustedHosts

同样,您可以通过执行以下命令通过 Windows PowerShell 控制台修改 TrustedHosts 属性:

Set-Item WSMan:\localhost\client\TrustedHosts –Value "*.contoso.com" –force

它是重要的是知道的构建基块和构建自定义的资源所需的概念。与有关的 CIM、 托管对象格式 (MOF)、 Windows PowerShell 和它的模块概念和 WinRM 是赖以建立了 DSC 资源的主要技术。

DSC 资源

在进入之前执行自定义的资源,它是重要的是知道如何打包和部署您的服务器上。DSC 资源都包含在 Windows PowerShell 模块,让你重新使用和分发的代码。你无论是作为 Windows PowerShell 脚本可执行模块或已编译二进制文件使用语言如 C#)。

DSC 使用模块基础设施来承载其资源。Windows PowerShell 模块应驻留在文件系统上的预先确定的文件夹位置。你可以通过取消 Windows PowerShell 变量 $env 来查看这些地点: ModulePath 从任何 Windows PowerShell 控制台。Windows PowerShell 安装所有的预置模块在 C:\Windows\System32\Windows­PowerShell\v1.0\modules。您应部署在 C:\Program Files\WindowsPowerShell\modules 的所有自定义模块。为信任­承载自定义的资源,用作 C:\Program Files\WindowsPowerShell\Modules 的 WinRM Windows PowerShell 模块模块基路径和宿主的容器。您将创建一个新的文件夹,在该文件夹中命名 WinRM。

DSC 预计资源,以遵循有关 Windows PowerShell 模块内的文件和文件夹结构的规则。所有的 DSC 资源应放在模块中的 DSCResources 文件夹中。这是根文件夹包含所有相关的资源。在 WinRM 文件夹中,创建名为 DSCResources 的另一个文件夹。此文件夹中承载 DSC 的所有资源。创建一个文件夹,该文件夹中的每个资源。在这种情况下,您要创建单个资源,因此,只是一个文件夹命名为 TrustedHosts。文件夹和资源应具有相同的名称。此特定于资源文件夹 TrustedHosts 包含特定于资源的文件。TrustedHosts 资源的文件夹结构显示在图 1

Windows PowerShell 所需状态配置资源文件夹和文件规范
图 1 Windows PowerShell 所需状态配置资源文件夹和文件规范

定义,并提供资源执行的文件是:

  1. << 资源名称 >>.psm1 — — 这是一个资源实现文件。在这种情况下,它是 TrustedHosts.psm1。
  2. << 资源名称 >>.psd1 — — 这是资源的元数据文件。在这种情况下,它是 TrustedHosts.psd1。如果您从.psm1 脚本文件导出强制性功能,不需要此文件。
  3. << 资源名称 >>。Schema.mof—This 是资源定义。在这种情况下,它是 TrustedHosts.Schema.mof。

每个 Windows PowerShell 模块都有一个清单文件与模块本身的名称相同的模块。生成模块的清单文件,使用新 ModuleManifest cmdlet 从 Windows PowerShell 控制台使用提升的权限,如下所示:

New-ModuleManifest -Path "C:\Program Files\WindowsPowerShell\Modules\WinRM\WinRM.psd1"

现在,你了解的文件夹和文件结构规则的包装 DSC 资源,我将重点放在实现文件上。

TrustedHosts.Schema.mof

您将使用 MOF 文件定义 CIM/WMI 使用来生成它们,使管理信息交换在数据中心内的实例的类。MOF 类定义其属性的 DSC 资源。属性可以包括读写、 只读和必需的属性。总是应该为 CIM/WMI 来唯一地标识对象的键属性。属性分配值,它们最终会变成要监测和维护的资源所需的状态。您可以生成 MOF 文件通过 MOF 设计器或作者他们通过任何文本编辑器 (如记事本,因为它们本质上是文本文件。

在创建之前的 MOF 类,分析您需要在任何一台机器上的 TrustedHosts 配置管理的信息。用于管理服务器上的 TrustedHosts,您需要三个属性:计算机名添加到 TrustedHosts 列表 ; 确保属性,以确定是否 ComputerName 应该添加或删除 ; 和凭据来访问和管理 WinRM TrustedHosts 配置。

然后对 TrustedHosts DSC 资源 MOF 类被创建,如下所示:

[ClassVersion ("1.0.0"), FriendlyName ("TrustedHosts")]
Class TrustedHosts: OMI_BaseResource
{
  [Key, Description ("Name of the host")] String ComputerName;
  [Write, ValueMap {"Present", "Absent"}, 
    Values {"Present", "Absent"}] string Ensure;
  [Write, EmbeddedInstance ("MSFT_Credential")] string Credential;
};

它是重要的是知道如何 MOF 类融入整个 CIM 生态系统。DSC 的所有资源都是从 OMI_BaseResource 抽象 CIM 类都派生的。此基类定义在 root\Microsoft\Windows\DesiredStateConfiguration 命名空间,并提供所需的 DSC 的所有资源的共同属性。

这些属性是 ResourceID、 SourceInfo、 ModuleName、 ModuleVersion 和恒大。MOF 文件在生成期间,会将这些属性添加到资源的所有属性。这些是特定于 DSC 的内部属性,只由 DSC 使用。类 TrustedHosts 也将成为 root\Microsoft\Windows\DesiredStateConfiguration 命名空间的一部分。所有三个属性是字符串类型,它们代表了 WinRM TrustedHosts 资源定义。

计算机名属性具有额外的元数据 — — 关键和描述。密钥类型限定符,指示此属性是强制性,此属性的值应该是唯一的所有实例。应该有至少一个具有内 MOF 类关键元数据属性。可关键元数据的多个属性。这将创建一个复合键,他们应该都是唯一的所有实例。我做此属性作为键,因为否则,我不能确定 TrustedHosts 列表中是否已存在的计算机名称。

描述提供给属性的文本意义。与出现对确保属性进行批注。出现是一组值的属性可以接受,是一个枚举的同义词。在这种情况下,确保可以接受"存在"或"无"作为其法律价值。确保和凭据属性都装饰着写属性。这意味着你可以写进,并在配置脚本中使用自定义资源时修改此属性。

类 TrustedHosts 还具有元数据 — — ClassVersion 和 FriendlyName。类版本,有利于保持相同的 MOF 类的多个版本。FriendlyName 是重要的元数据,因为配置脚本使用它来引用资源。配置脚本识别资源通过他们的友好名称。在 TrustedHosts 类中,类名称和友好名称是相同的。你可以阅读更多关于 MOF 文件定义和及其元数据在 bit.ly/1AEHLTj

后定义的 MOF 类,坚持以 ascii 格式或 Unicode 格式文件名内 TrustedHostsfolder TrustedHosts.Schema.mof 在文件系统上的创建早些时候。当你在 DSC 配置中使用此自定义的资源时,您会将值分配给这些属性,以指示所需的配置状态。

TrustedHosts.psm1

现在我将介绍最关键的一个自定义的 DSC 资源 — — 资源模块脚本。这包含自定义资源实现,并且确定它将如何工作。它还通过保持它符合预期的配置管理的资源。每个 DSC 资源脚本模块必须实现三个功能。这些是强制的每个函数具有规则及其执行。

Get-TargetResource:此函数必须声明并接受 MOF 类中的所有属性都标记为关键作为参数。它也可以接受所需,并写入属性作为参数。键和所需的参数应申报参数时作为强制性标记。

Get TargetResource 函数应执行脚本来检索资源使用 Key 属性值的当前状态。它应该返回一个哈希表,包含所有属性与值的 MOF 类中定义为资源的当前状态。

集 TargetResource: 此函数必须声明并接受 MOF 类中的所有属性标记为键,所需和写入属性作为参数。键和所需的参数应申报参数时作为强制性标记。集 TargetResource 函数应执行脚本应使用所有资源的属性值提供参数,以获得资源实例,并执行以下操作之一操作:

  • 创建一个新的资源实例。
  • 更新现有资源实例。
  • 删除现有的资源实例。

集 TargetResource 函数不返回任何值。

TargetResource 测试: 测试 TargetResource 功能类似于一套 TargetResource 的功能,但它不会返回当前资源状态。相反,它将返回 true 或 false 取决于当前资源状态是否符合预期的资源状态配置脚本中所指定的布尔值。如果有两个国家之间的完全匹配,此函数返回 true。它将返回 false 如果在任何键上不匹配的要求,或写入属性。

这三项功能由 DSC 的本地配置管理器 (LCM) 组件调用。液晶显示模块是在所有运行 Windows 管理框架 4.0 的计算机上运行的代理。LCM 看作是 DSC 客户端在网络中的所有服务器上可用。DSC 可与此客户端进行通信,并提供必要的配置信息。它是 LCM (DSC 客户端) 以管理、 监测和确保在目标计算机上的配置符合要求的责任。要做它的工作,它会调用 DSC 资源功能。它首先调用测试 TargetResource 函数,以确定是否资源配置匹配预期的配置。如果返回的值为 true,这意味着资源的当前状态是预期的配置相同。然而,它应该返回如果状态不匹配,则为 false。

如果 LCM 获取假作为返回值,它将调用设置 TargetResource,将执行以下操作:

  1. 如果该资源不存在,并且确保属性设置为当前,它将创建新实例的资源和属性赋值,因此,它从配置脚本中获取。
  2. 如果存在相应的资源并确保属性设置为当前,但一些其属性值不匹配的属性值,在配置脚本中编写,这一职能应更新只是这些资源的属性。
  3. 如果存在相应的资源并确保属性设置为当前且所有属性值都匹配编写配置脚本中的属性值,该函数应该做什么。
  4. 如果存在相应的资源,确保属性设置为无,则它应被删除。
  5. 如果不存在相应的资源,确保属性设置为无,那么它应该做什么。

现在是时候要实现这些 TrustedHosts 的自定义资源相关的三个功能。打开 Windows PowerShell ISE,执行这三项功能并将其保存在 WinRM\DSC­Resources\TrustedHosts TrustedHosts.psm1 同名的目录。

Get TargetResource TrustedHosts 自定义资源

此函数接受作为参数在 MOF 文件中定义的所有属性。凭据参数是类型 PSCredential。这是由 Windows PowerShell 公开用于捕获和存储的用户名和密码的 Microsoft.NET 框架类。其余参数是字符串类型。

在这个函数中,创建哈希表用于返回当前的资源属性,如中所示图 2。它使用 WSMAN 提供程序读取当前的 TrustedHosts 配置列表。该脚本检查看看是否有任何计算机名称列表中。如果有,它遍历,并且与它们提供的计算机名称相匹配。如果它找到确切的匹配项,它将更新确保属性与价值的礼物或无价值。它还向返回的哈希表中添加相同的计算机名称。

图 2 Get TargetResource 函数的 TrustedHosts 资源

Function Get-TargetResource
{
  param(
  # Computer name to be checked within TrustedHosts List
  [parameter(mandatory)]
  [string] $ComputerName,
  # This property determines whether computer name
  # should be added or removed from TrustedHosts
  [parameter(mandatory)]
  [string] $Ensure,
  # Credentials needed to manage WinRM TrustedHosts configuration
  [parameter(mandatory)]
  [PSCredential] $Credential
  )
  # Hashtable containing values is returned from this function
  $retval = @{}
  $retval.Add("Ensure","")
  $retval.Add("ComputerName", "")
  try{
    # Get current TrustedHosts comma-separated list using supplied credentials
    $TH = $(Get-Item WSMan:\localhost\Client\TrustedHosts `
      -credential $Credential).value
    Write-Verbose "Current TrustedHosts Configuration has $TH"
    if($TH.Length -gt 0){
      $temp = $TH -split ","
      [string] $newNode = ""
      # Check if value in TrustedHosts list already have few computer names
      if($temp.Length -gt 0) {
        for($i = 0; $i -lt $temp.Length; $i++){
          if($temp[$i].Trim() -eq $ComputerName.Trim())
          {
            $retval.Ensure = "Present"  # Found computer name
            $retval.ComputerName = $ComputerName
            break;
          } else {
            $retval.Ensure = "Absent" # Computer name is not in the list
            $retval.ComputerName = $ComputerName
          }
        }
      }
    } else {
      # TrustedHosts list is empty
      $retval.Ensure = "Absent"
      $retval.ComputerName = $ComputerName
    }
  } catch {
     Write-Verbose " Error executing Get-TargetResource function"
     Write-Verbose $Error[0].Exception.ToString()
  }
}

集 TargetResource TrustedHosts 自定义资源

此函数也采用相同的参数集作为获取目标­资源。在此函数中,你可以使用 WSMAN 提供从 TrustedHosts 列表中的当前计算机名称。然后,确保属性的值,根据计算机名称添加或从 TrustedHosts 列表中删除。

如果的值为当前,计算机名称添加到列表中。如果值不存在,它被移除。两种操作通过设置项目 cmdlet 和 WSMAN 提供程序,如中所示图 3。还有从函数没有返回值。您应该使用写详细使用详细的开关时,在控制台上提供额外的反馈。

图 3 套 TargetResource 函数的 TrustedHosts 资源

Function Set-TargetResource
{
  param(
  # Computer name to be added within TrustedHosts List
  [parameter(mandatory)]
  [string] $ComputerName,
  # This property determines whether computer name should be
  # added or removed from TrustedHosts
  [parameter(mandatory)]
  [string] $Ensure,
  # Credentials needed to manage WinRM TrustedHosts configuration
  [parameter(mandatory)]
  [PSCredential] $Credential
  )
  try {
    # Get current TrustedHosts comma-separated list
    # using supplied credentials
    $TH = (Get-Item WSMan:\localhost\Client\TrustedHosts `
      -credential $Credential).value
    # Computer name should be added to the TrustedHosts list
    if($Ensure -eq "Present") {
      Write-Verbose "The current value is $TH"
      if($TH.Length -gt 0)
        {
          # Adding Computer name when the TrustedHosts list
           # configuration is not empty
          $TH += ",$ComputerName"
          Set-Item WSMan:\localhost\Client\TrustedHosts `
            -Value $TH -credential $Credential -FORCE
        } else {
          # Adding Computer name when the TrustedHosts list
           # configuration is empty
          $TH += "$ComputerName"
          Set-Item WSMan:\localhost\Client\TrustedHosts `
            -Value $TH -credential $Credential -FORCE
        }
          Write-Verbose "The New value is $TH"
      } else {
        # Computer name should be removed from TrustedHosts list
        Write-Verbose "The current value is $TH"
        if($TH.Length -gt 0) {
          $temp = $TH -split ","
          [string] $newNode = ""
          if($temp.Length -gt 0)
            {
               for($i = 0; $i -lt $temp.Length; $i++){
                 if($temp[$i].Trim() -ne $ComputerName.Trim())
                   {
                     $newNode += $temp[$i] + ","
                   }
                 }
                         $newNode = $newNode.TrimEnd(",")
                    # Updating list after removing the Computer name
                    Set-Item WSMan:\localhost\Client\TrustedHosts -Value $newNode
                      -credential $Credential -Force
                    Write-Verbose "The New value is $TH"
               }
             }
     }
   } catch {
      Write-Verbose " Error executing Set-TargetResource function"
      Write-Verbose $Error[0].Exception.ToString()
   }
}

测试 TargetResource TrustedHosts 自定义资源

这个函数是类似于得到 TargetResource。唯一的区别是它从这个函数返回布尔值 True 或 False。首先,查询使用 WSMAN 提供程序的 TrustedHosts 属性的当前值。因为可能有多个以逗号分隔的计算机名称,您会需要遍历它们。

同时循环,如果你找到匹配与配置提供的名称的计算机名称,请检查是否应出席或缺席。如果确保属性值是礼物和 TrustedHosts 设置中存在该计算机名称,则返回值设置为 True。如果确保属性值是缺席和 TrustedHosts 设置中存在该计算机名称,则返回值设置为 False。

如果确保属性值是当前计算机名称中的 TrustedHosts 设置不存在,则返回值设置为 False。如果确保属性值是缺席中 TrustedHosts 设置计算机名称不存在,返回的值设置为 True,如中所示图 4

图 4 测试 TargetResource 函数的 TrustedHosts 资源

Function Test-TargetResource
{
  param(
  # Computer name to be added within TrustedHosts List
  [parameter(mandatory)]
  [string] $ComputerName,
  # This property determines whether computer name should
  # be added or removed from TrustedHosts
  [parameter(mandatory)]
  [string] $Ensure,
  # Credentials needd to manage WinRM TrustedHosts configuration
  [parameter(mandatory)]
  [PSCredential] $Credential
  )
  # Boolean return variable from this function
  $retval = $false
  try {
    # Get current TrustedHosts comma-separated list using supplied credentials
    $TH = (Get-Item WSMan:\localhost\Client\TrustedHosts `
      -credential $Credential).value
    if($TH.Length -gt 0) {
      $temp = $TH -split ","
      [string] $newNode = ""
      if($temp.Length -gt 0) {
        for($i = 0; $i -lt $temp.Length; $i++){
          if($temp[$i].Trim() -eq $ComputerName.Trim()) {
            # Computer name exists within TrustedHosts list
            if($Ensure -eq "Present")
            {
              # Computer name exists and expected to be present
              $retval= $true
              break;
            } else {
              # Computer name exists and is not expected to be present
              $retval = $false
            }
            break;
          } else {
            # Computer name does not exist
            if($Ensure -eq "Present")
            {
              $retval = $false
            } else {
              $retval = $true
            }
          }
        }
      }
    } else {
      # TrustedHosts list is empty
      if($Ensure -eq "Present"){
        $retval= $false
      } else {
        $retval = $true
      }
    }
      return $retval
  } catch {
    Write-Verbose " Error executing Test-TargetResource function"
    Write-Verbose $Error[0].Exception.ToString()
  }
}

导出自定义资源功能

实施后的三个强制性 DSC 资源功能,您应该将它们导出。有两种方法执行此操作:

  1. 创建一个 Windows PowerShell 模块清单.psd1 文件旁边的.psm1 文件。
  2. 导出的函数从脚本文件本身使用出口 ModuleMember cmdlet。

使用第二种方法导出的函数。在此方法中,从使用出口 ModuleMember cmdlet 的模块导出带有 TargetResource 后缀的所有功能。如果你有兴趣在使用第一种方法,按照相同的步骤,以前用于创建为每个 DSC 资源模块的清单文件。首先,执行新 ModuleManifest 命令和存储生成的.psd1 与.psm1 TrustedHosts 文件夹中和。Schema.mof 文件:

# Exports the three functiona as part of the module
Export-ModuleMember -Function *-TargetResource

在配置中使用自定义资源 TrustedHosts

接下来,您将创建一个配置脚本来使用新的 TrustedHosts 自定义资源。将此配置应用到本地主机节点并提供给 TrustedHosts 资源三个属性的值。配置脚本以强制性的凭据类型的参数的 [PSCredential]。此参数分配给该资源的凭据属性,如中所示图 5

图 5 Get TargetResource 函数的 TrustedHosts 资源

Configuration TestWinRMTrustedHosts
{
  param([parameter(mandatory)] [pscredential] $Credential)
  import-dscresource -modulename WinRM
  node localhost
    {
      TrustedHosts TrustedHostEntry
        {
          ComputerName = "Client01.Contoso.com"
          Ensure ="Present"
          Credential = $Credential
        }
    }
}

默认情况下,DSC 不让您在纯文本中使用的凭据。执行此操作通过配置数据哈希表。在哈希表,添加具有真正的 PSDSCAllowPlainTextPassword 属性作为其值。使用 PSDSCAllowPlainTextPassword 是一个安全风险,因为它允许您将密码存储为纯文本在 MOF 文件中。应使用安全证书作为一种最佳做法,以确保密码并不存储为纯文本在 MOF 文件和安全地传输使用此配置数据:

$ConfigData = @{
AllNodes = @(
  @{
    NodeName = "localhost"
    PSDscAllowPlainTextPassword = $true   
  }
  )
}

接下来,创建一个配置对象,通过执行配置脚本并在 MOF 文件中,配置的位置传递­数据变量和凭据用 Get 凭据的命令:

TestWinRMTrustedHosts -OutputPath "C:\CR" -ConfigurationData $ConfigData `
  -credential (get-credential)

执行此命令生成一个 MOF 文件,命名为 localhost.mof C:\CR 地点。当这执行时,它会要求输入用户名和密码。后生成 MOF 文件,它是应用在推送模式下配置的时间。通过执行开始 DscConfiguration 命令来执行此操作:

Start-DscConfiguration -Wait -Force -Path "C:\CR" -Verbose

应用配置之后, 检查 localhosts WinRM TrustedHosts 设置的值。ComputerName 值 DC01 现在应该 TrustedHosts 值的一部分。您可以添加或删除使用此自定义的资源的 TrustedHosts 值的计算机名称。

导入 DSCResource

您将使用导入 DSCResource,将自定义资源导入到配置脚本以执行设计时验证的自定义资源。这是另一种方式,以确保好撰写了自定义的资源。自定义资源,可在 C:\ProgramFiles\WindowsPowerShell\Modules。要导入或加载这些模块,请使用导入 DSCResource 函数。导入 DSCResource 是一个动态的功能,仅在配置脚本中可用。此函数不能超出配置的块,它又非位置的两个参数:

  1. ModuleName — — 应导入的模块的名称。
  2. 名称 — — 应导入的资源的名称。

如果只提供了 ModuleName 参数和省略名称,可用模块中的所有资源将被导都入。你可以使用这种方法来加载配置示例中的 TrustedHosts 资源:

Import-DSCResource –ModuleName WinRM

如果只提供了名称参数,并且省略了 ModuleName,DSC 将搜索所有的模块位置,可从 $env: PSModulePath,若要查找资源。一旦被发现,它导入的资源:

Import-DSCResource –Name TrustedHosts

如果提供了 ModuleName 和名称的参数,该资源加载和导入从提供的模块名称。这是迄今为止最快的机制,以发现和加载资源,因为 DSC 不必执行广泛的搜索:

Import-DSCResource –ModuleName WinRM –ResourceName TrustedHosts

您可以在同一时间使用逗号-加载多个资源­分离以及资源名称。

结束语

DSC 为您提供了必要的扩展,以便创建新资源并在配置中使用它们。Windows PowerShell 实施强制性的一些函数,从而简化了创作 DSC 自定义资源。这些职能应遵循几条规则在其执行情况。

如果开箱提供的资源或从社会不能满足您的需要,您可以轻松创建您自己。这篇文章向您展示了如何创建一个简单的自定义资源具有完整的实施。它还显示如何你应该包装在 Windows PowerShell 模块中,这些资源由规则规管其文件和文件夹的结构。


Ritesh Modi 是一位建筑师与 Microsoft 服务。他有超过十年的经验,构建和部署企业级解决方案。他是 Windows PowerShell,所需状态配置和系统中心的专家。他曾在 TechEd,内部培训和在博客 automationnext.wordpress.com。联系到他在 rimodi@microsoft.com

衷心感谢以下技术专家对本文的审阅:Abhik · 查特吉