Windows 管理

使用 Windows PowerShell 简化组策略管理

Thorbjörn Sjövold

 

概览:

  • 什么是 Windows PowerShell?
  • 以前如何管理 GPO
  • 将脚本迁移到 Windows PowerShell

Microsoft 组策略技术不是在一夜间就流行起来的,这一技术在某种程度上比较难以理解,而且需要采用 Active Directory,在当时,Active Directory 服务才刚刚出现,与当时被奉为标准的 Account/Resource 域毫无相似之处,人们对此都很陌生。而时至今日,组策略

几乎成为每个组织管理 Windows® 基础结构的主要手段。我有种感觉,Windows PowerShell™ 作为 Microsoft 最新的管理技术,将完全肩负起同样的使命,成为主要的管理手段。实际上,Windows PowerShell 很可能会使身为组策略管理员的您工作起来更加轻松。

在本文中,我将为您介绍在 Windows PowerShell 中,如何直接使用为 VBScript (以及通常基于 COM 的脚本语言)之类的 Windows Scripting Host 语言编写的 Microsoft® 组策略管理控制台 (GPMC) API 在自己特有的环境中简化组策略的管理。

为组策略任务编写脚本

几年前 Microsoft 发布了 GPMC,组策略管理员突然发现有许多简便易用的功能可供自己使用。具体地说,以组策略为中心的 MMC 管理单元体现了组策略管理向前迈进了一大步,特别是相对于使用 Active Directory® 的用户和计算机而言尤其如此。不仅如此,管理员还可以借助一个全新的 API,使用基于 COM 的语言(如 VBScript)来执行组策略管理任务,例如备份和还原组策略对象 (GPO)、在域之间迁移 GPO、配置有关 GPO 和链接的安全设置以及编写报告。

但遗憾的是,GPMC 并没有提供对组策略对象内实际已配置设置进行编辑的功能。换句话说,您可以对 GPO 容器执行操作,例如,读取 GPO 版本、读取修改日期、创建全新的 GPO、从不同的域中备份和还原/导入 GPO,等等,但却不能以编程的方式添加或更改 GPO 的内容,例如添加新的重定向文件夹或新的软件安装。通常,您只能使用组策略对象编辑器手动创建 GPO 并配置所有设置,然后备份该 GPO,再将其导入测试环境中。只有当各项功能经过验证可以正常工作时,您才能将其导入实际的应用环境中。尽管缺少这一功能,但使用脚本而不是与 GPMC API 进行人工交互也为日常的策略组管理节省了大量的时间、精力,同时也避免了不少错误。

下一级别

Windows PowerShell 与 VBScript 之类的脚本语言有何不同?对于初学者而言,Windows PowerShell 是一种外壳程序,至少就目前来说,您可以将其视为一种命令行解释器。尽管 VBScript 可以从命令行运行,但 VBScript 文件却不能逐行运行。相比之下,Windows PowerShell 脚本则可以作为一系列单独命令动态地创建。此外,Windows PowerShell 还具有一些功能类似于 VBScript 中的子例程的函数,它们可在 Windows PowerShell 命令提示符下实时地进行创建。

更值得一提的是,Windows PowerShell 基于 Microsoft .NET Framework,而 VBScript 则依赖于早期的 COM 技术。这意味着,目前有大量的 .NET 代码可直接在 Windows PowerShell 内使用。

总而言之,利用 Windows PowerShell,您可以获得完全的脚本支持和交互模式,多种功能集于一身。本文提供的示例均为命令行输入内容,因此您可以一边看示例一边尝试着输入,不过,如果您将它们放入 Windows PowerShell 脚本文件运行,它们也同样能正常工作。

使用 Windows PowerShell 重新创建早期脚本

在开始采用一项新技术时,您要做的最后一件事就是抛弃以前的全部工作成果。您可以使用三种方法从 GPMC API 访问 COM 对象,或者主要是重新利用任何早期 VBScript。下面是可选的三种方法:

  • 使用 C# 或托管 C++ 等编程语言编写一个 Windows PowerShell cmdlet。
  • 使用 Windows PowerShell 访问 MSScript.ocx 中的 ScriptControl,以封装早期脚本。
  • 将 COM 调用封装在可重用的 Windows PowerShell 函数中或直接调用 COM 对象。

我将重点介绍第三种方法,但首先让我们简单了解一下所有方法。

创建 Windows PowerShell Cmdlet

Microsoft 在 Windows PowerShell 中放入了大量的 cmdlet,这些 cmdlet 允许您复制文件、将输出内容格式化、检索日期和时间等,但您也可以自己创建 cmdlet。有关整个过程的完整详细记录,请访问 msdn2.microsoft.com/ms714598.aspx。概括地讲,主要步骤如下所示:

  • 使用 .NET 编程语言(如 C#)创建一个类库 DLL。
  • 创建一个新类并从基类 cmdlet 继承。
  • 设置各种属性,以确定名称、用法、输入参数等,并添加您的代码。

由于 Windows PowerShell 基于 .NET Framework,任何作为参数返回或传递的类型(如字符串、对象等)在代码中与在 Windows PowerShell 中都是完全相同的,不需要进行任何特殊类型的转换。

这一解决方案真正强大之处在于您将拥有一种完全受自己支配的编程语言。

使用 MSScript.ocx 中的 ScriptControl 对象封装早期脚本

显然,您需要 VBScript 引擎来运行 VBScript 文件。令人不太注意的是,该引擎是 COM 对象,而且由于您可以从 Windows PowerShell 使用 COM 对象,因此可以调用 VBScript 引擎。过程如下所示:

$scriptControl = New-Object -ComObject ScriptControl
$scriptControl.Language = ‘VBScript’
$scriptControl.AddCode(
    ‘Function ShowMessage(messageToDisplay)
    MsgBox messageToDisplay
    End Function’)
$scriptControl.ExecuteStatement(‘ShowMessage
    “Hello World”’)

如果您在 Windows PowerShell 命令行界面 (CLI) 中输入此代码,则会由某参数调用并执行 VBScript 函数,从而显示一个带有文本“Hello World”的消息框。

这时有些人可能会想:“太棒了!我已经掌握了从 Windows PowerShell 使用 COM 的技巧,不用再继续读这篇文章,马上开始使用我的早期 GPMC 脚本填充 ScriptControl 对象吧。”很遗憾,事情没有那么简单。随着脚本的增大,这种方法很快就会变得非常复杂而繁琐。

封装 COM 对象

由此可见,第三种方法是最好的:将 COM 调用封装在可重用的 Windows PowerShell 函数中,这样您就能够在 GPMC API 中使用 COM 对象。下列代码将说明如何直接在 Windows PowerShell 中创建 .NET 对象。在此示例中,可以使用 FileInfo 对象获得文件的大小:

$netObject = New-Object System.IO.FileInfo(
“C:\boot.ini”) # Create an instance of FileInfo 
               # representing c:\boot.ini

请注意,“#”在 Windows PowerShell 中用于表示行内注释。使用此新实例化的 FileInfo 对象,只要键入以下代码即可轻松获得 boot.ini 的大小:

$netObject.Length # Display the size in bytes of the
                  # file in the command line interface

等等,我们不是说要谈论 COM 对象和 VBScript 转换吗?没错,但请看以下命令:

$comFileSystemObject = New-Object –ComObject Scripting.FileSystemObject

您会发现,此处的语法与我之前从 .NET Framework 创建本地对象时所用的基本相同,差别只有两点:第一,我添加了 –ComObject 开关,将 Windows PowerShell 指向了 COM 环境,而不是 .NET 环境。第二,我使用的是 COM ProgID,而并非 .NET 构造函数,在本例中为 Scripting.FileSystemObject。这里的 ProgID 是您经常使用的名字。在 VBScript 中,等同的代码应为:

Set comFileSystemObject = CreateObject(
    “Scripting.FileSystemObject”)

若要使用 VBScript 获得该文件的大小,请将上面的代码行连同以下代码一起添加到一个文件中:

Set comFileObject = comFileSystemObject.GetFile(
    “C:\Boot.ini”)
WScript.Echo comFileObject.Size

然后,使用 Cscript.exe 等运行它。在 Windows PowerShell 中,您可以按如下所示获得文件大小(如果您愿意,可以从 Windows PowerShell 命令行直接输入):

$comFileObject = $comFileSystemObject.GetFile(
    “C:\boot.ini”)
$comFileObject.Size

当然,我本可以使用管理驱动器内对象的 Windows PowerShell cmdlet 对读取文件大小的 VBScript 进行转换,但我想向您展示从 Windows PowerShell 访问 COM 是多么容易。请注意,虽然我指示 Windows PowerShell 创建的是一个 COM 对象,但实际创建的对象(此处为 $comFileSystemObject)是一个封装了该 COM 对象并公开了其接口的 .NET 对象。不过,就本文而言,这样做没有任何影响。

运行状态的 Windows PowerShell

现在,您已经知道如何从 Windows PowerShell 访问 COM,接下来,我们要专门介绍组策略。此处的示例将展示一些简短的代码段,让您对如何从 Windows PowerShell 使用 GPMC API 有一个大致的概念;您可以从本文相关的代码下载处,在线为 technetmagazine.com/code07.aspx,下载到一整套 Windows PowerShell 函数集来管理组策略。图 1 所列为下载内容中包含的各种函数。

Figure 1 下载内容中的自定义函数

函数名称 说明
BackupAllGpos 备份域内所有的 GPO
BackupGpo 备份单个 GPO
RestoreAllGpos 将备份内的所有 GPO 还原到域
RestoreGpo 还原备份内的单个 GPO
GetAllBackedUpGpos 从给定路径检索最新版本的 GPO 备份
CopyGpo 将一个 GPO 内的设置复制到另一 GPO
CreateGpo 新建空的 GPO
DeleteGpo 删除 GPO
FindDisabledGpos 在用户和计算机均被禁用时返回所有 GPO
FindUnlinkedGpos 返回所有无链接的 GPO
CreateReportForGpo 为域内的单个 GPO 创建 XML 报告
CreateReportForAllGpos 为域内的每个 GPO 创建单独的 XML 报告
GetGpoByNameOrID 按其显示名称或 GPO ID 查找 GPO
GetBackupByNameOrId 按其显示名称或 GPO ID 查找 GPO 备份
GetAllGposInDomain 返回域内所有的 GPO

在阅读本节内容时,您可以随意启动 Windows PowerShell 命令行并键入命令。但是请记住,有些命令要依赖于前面的命令。也就是说,最初创建的一些对象在以后也会用到,因此,请在同一个 Windows PowerShell 会话中执行操作。如果您关闭该会话,则必须从头开始,重新键入所有命令。

好,现在就让我们先用 Windows PowerShell 创建一个新的 GPO。Microsoft 的组策略团队随 GMPC 提供了许多能够运行正常的 VBScript 示例,您可以利用它们加快工作速度。这些示例位于 %ProgramFiles%\GPMC\Scripts 目录中,您还可以在该目录下找到一个包含 GPMC API 文档的 gpmc.chm 文件。接下来让我们看看 CreateGPO.wsf 脚本,仔细研究一下它为何能够正常运行。

在脚本的开头,您会发现这么一行:

Dim GPM
Set GPM = CreateObject(“GPMgmt.GPM”)

这基本上是所有组策略管理会话或脚本的起点,因为是它实例化了 GPMgmt.GPM 类,从而使大多数 GPMC 功能都能够被访问。接下来,我们转而从 Windows PowerShell 执行这一操作:

$gpm = New-Object -ComObject GPMgmt.GPM

现在,您已获得了组策略管理的起点,接下来要了解使用它能够做些什么。通常您需要阅读相关文档来获得此类信息,但 Windows PowerShell 提供了一个非常酷的功能。如果键入以下一行,您可以得到如图 2 中所示的输出:

图 2 Get-Member 输出

图 2** Get-Member 输出 **(单击该图像获得较大视图)

$gpm | gm

我觉得这个功能真的很棒。请注意观察,看 Get-Member (or gm) cmdlet 是如何允许您直接从命令行查看该对象所支持的属性和方法的。这当然不能等同于阅读文档,但如果您不记得参数的确切个数或确切名称等,该功能可以让您轻松使用熟悉的对象。请牢记一点,在您查看 GPMC 文档节点列表时,会发现它很象 GPM 对象,而且所有其他类均带有前缀字母 I;这是由于 COM 的内部工作方式而造成的,我们不需要过分关注这一点;这是专为 C++ 程序员编写本地 COM 代码准备的,用于表示界面和实现它的类之间的差异。另外还要注意,使用 GPMC API 时,只有一个对象需要您采用此方法来创建,那就是 GPMgmt.GPM;其他所有对象均使用该 GPM 对象开始的方法来创建。

现在,我们要继续创建新的 GPO。

图 3 说明了创建一个 GPO 是如此的简单。请注意,在这里我省去了一些代码,包括对错误的处理(例如,如果您无权创建 GPO 会出现什么情况),并且我已对域名进行硬编码,这些都是您应该了解的。

Figure 3 创建 GPO

$gpmConstants = $gpm.GetConstants() 
# This is the GPMC way to retrieve all 
# constants
$gpmDomain =$gpm.GetDomain(“Mydomain.local”, “”, $gpmConstants.UseAnyDC)
# Connects to the domain where the GPO should 
# be created, replace Mydomain.local with the 
# name of the domain to connect to.
$gpmNewGpo = $gpmDomain.CreateGPO() 
# Create the GPO
$gpmNewGpo.DisplayName = “My New Windows PowerShell GPO” 
# Set the name of the GPO

现在,您已经学会如何创建 GPO,让我们打开一个现有的 GPO。您仍具有对域 $gpmDomain 的引用,因此请键入以下内容:

$gpmExistingGpo = $gpmDomain.GetGPO(
  “{31B2F340-016D-11D2-945F-00C04FB984F9}”) 
# Open an existing GPO based on its GUID, 
# in this case the Default Domain Policy.
$gpmExistingGpo.DisplayName 
# Show the display name of the GPO, it 
# should say Default Domain Policy
$gpmExistingGpo.GenerateReportToFile($gpmConstants.ReportHTML, “.\DefaultDomainPolicyReport.html”

上述代码以 HTML 的形式完整报告了 Default Domain Policy 中的各项设置,但是很明显,您可以使用任何一种方法和属性(如 ModificationTime,它说明该 GPO 最后被修改的时间)来得知该 GPO 中所有设置的更改时间。

这是非常实用的。例如,您很可能遇到过这类情况:电话疯狂地响个不停,用户纷纷来电抱怨说他们的计算机运转异常。您怀疑这是更改、添加或删除了 GPO 设置造成的,但没有线索,不知该从哪个 GPO 着手。此时,Windows PowerShell 可以救急!只要在 Windows PowerShell 命令行中输入图 4 所示的脚本,就可以获得过去 24 小时内更改的所有 GPO。

Figure 4 发现修改过的 GPO

$gpmSearchCriteria = $gpm.CreateSearchCriteria() 
# We want all GPOs so no search criteria will be specified
$gpmAllGpos = $gpmDomain.SearchGPOs($gpmSearchCriteria) 
# Find all GPOs in the domain
foreach ($gpmGpo in $gpmAllGpos)
{
if ($gpmGpo.ModificationTime -ge (get-date).AddDays(-1)) {$gpmGpo.DisplayName}
# Check if the GPO has been modified less than 24 hours from now 
}

请注意 –ge 运算符,它表示大于或等于。如果您习惯使用其他脚本或编程语言中的 < 和 > 运算符,可能会有些不习惯。但由于这些运算符主要用于重定向,例如,将输出重定向到文件,因此在 Windows PowerShell 中不可将其用作比较运算符。

结束语

图 5 所列为将一个 GPO 的设置复制到另一 GPO 中的完整脚本。现在,您应该能清楚了解如何将这一新技术用于组策略,如何才能重新使用任何 COM 对象或利用 COM 对象的 VBScript 代码。

Figure 5 将一个 GPO 内的设置复制到另一 GPO

###########################################################################
# Function  : CopyGpo
# Description: Copies the settings in a GPO to another GPO
# Parameters : $sourceGpo     - The GPO name or GPO ID of the GPO to copy
#           : $sourceDomain   - The dns name, such as microsoft.com, ofthedomain where the original GPO is located
#           : $targetGpo      - The GPO name of the GPO to add
#           : $targetDomain   - The dns name, such as microsoft.com, of the domain where the copy should be put
#           : $migrationTable - The path to an optional Migration table to use when copying the GPO
# Returns   : N/A
# Dependencies: Uses GetGpoByNameOrID, found in article download
###########################################################################
function CopyGpo(
 [string] $sourceGpo=$(throw ‘$sourceGpo is required’),
 [string] $sourceDomain=$(throw ‘$sourceDomain is required’),
 [string] $targetGpo=$(throw ‘$targetGpo is required’),
 [string] $targetDomain=$(throw ‘$targetDomain is required’),
 [string] $migrationTable=$(“”),
 [switch] $copyAcl)
{
 
 $gpm = New-Object -ComObject GPMgmt.GPM # Create the GPMC Main object
 $gpmConstants = $gpm.GetConstants() # Load the GPMC constants
 $gpmSourceDomain = $gpm.GetDomain($sourceDomain, “”, $gpmConstants.UseAnyDC) # Connect to the domain passed 
                                                                              # using any DC
 $gpmSourceGpo = GetGpoByNameOrID $sourceGpo $gpmSourceDomain
 # Handle situations where no or multiple GPOs was found
 switch ($gpmSourceGpo.Count)
 {
   {$_ -eq 0} {throw ‘No GPO named $gpoName found’; return}
   {$_ -gt 1} {throw ‘More than one GPO named $gpoName found’; return} 
 }
 if ($migrationTable)
 {
   $gpmMigrationTable = $gpm.GetMigrationTable($migrationTable)
 }

 $gpmTargetDomain = $gpm.GetDomain($targetDomain, “”, $gpmConstants.UseAnyDC) # Connect to the domain passed 
                                                                              # using any DC

 $copyFlags = 0
 if ($copyAcl)
 {
   $copyFlags = Constants.ProcessSecurity
 }
 $gpmResult = $gpmSourceGpo.CopyTo($copyFlags, $gpmTargetDomain, $targetGpo)
 [void] $gpmResult.OverallStatus
 
}

正如组策略一样,Windows PowerShell 必将成为任何 Windows 管理环境的组成部分。但面对数百万行需要进行迁移或维护的 VBScript 代码,希望此教程会对您有所帮助。

您可以使用许多资源来改进组策略管理和其他先前使用 VBScript 的领域,这些资源包括下载内容中的 Windows PowerShell 函数,另外 TechNet 网站上还提供了 VBScript 到 Windows PowerShell 的转换准则,这一极为实用的准则在您了解 VBScript 中的等同内容的情况下,可以提示您如何在 Windows PowerShell 中执行常见任务。您可以从 microsoft.com/technet/scriptcenter/topics/winpsh/convert 获得该转换准则。

此外,GPMC API 也提供完整的文档内容,您可以从组策略网站 (microsoft.com/grouppolicy) 下载相应的信息。

最后但并非最不重要的一点,如果您尚未安装 Windows PowerShell,那还在等什么呢?现在就从 microsoft.com/powershell 下载吧。祝您愉快!

Thorbjörn Sjövold是 Special Operations Software (www.specopssoft.com) 的 CTO 和创始人,该公司提供基于组策略的系统管理和安全扩展产品。您可以通过 thorbjorn.sjovold@specopssoft.com 与他联系。

© 2008 Microsoft Corporation 与 CMP Media, LLC.保留所有权利;不得对全文或部分内容进行复制.