共用方式為


Windows 系統管理

透過 Windows PowerShell 簡化群組原則的管理

Thorbjörn Sjövold

 

摘要:

  • 什麼是 Windows PowerShell?
  • GPO 過去是怎麼管理的
  • 將指令碼遷移到 Windows PowerShell

Microsoft 群組原則技術並不是花個晚上就能弄懂,它稍微複雜,而且它必須採用 Active Directory,這項奇怪的新服務看起來跟當時標準的帳戶/資源網域完全是兩個樣。當今的群組原則

幾乎是各家擁有 Windows® 基礎結構的公司的管理骨幹。我可以預見 Microsoft 推出的這項最新管理技術 Windows PowerShell™ 也會有一樣的成就。事實上,Windows PowerShell 很有可能讓身為群組原則管理員的您的工作更加倍輕鬆。

在本文中,我將說明如何直接從 Windows PowerShell 取用針對 Windows Scripting Host 語言,如 VBScript (以及一般以 COM 為主的指令碼語言),所撰寫的 Microsoft® 群組原則管理主控台 (GPMC) API,來簡化在您的環境中管理群組原則的方式。

編寫群組原則工作的指令碼

幾年前,當 Microsoft 發行 GPMC 時,群組原則管理員們手邊突然多了一堆有用的功能。尤其是以群組原則為主的 MMC 嵌入式管理單元,特別在與 Active Directory® 使用者和電腦相較之下,更是讓群組原則管理向前邁出了一大步。再者,還有嶄新的 API 讓系統管理員能使用以 COM 為主的語言 (如 VBScript) 來執行群組原則的管理工作,例如備份和還原群組原則物件 (GPO),在網域間遷移 GPO,設定 GPO 上的安全性設定以及連結和報告等。

可惜的是,GPMC 並沒有提供在群組原則物件內編輯實際設定內容的功能。也就是說,可以在 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# 或 Manged C++,來建立 Windows PowerShell cmdlet。
  • 使用 Windows PowerShell 存取 MSScript.ocx 中的 ScriptControl 來包裝舊指令碼。
  • 將 COM 呼叫包裝在可重複使用的 Windows PowerShell 函數中,或直接呼叫 COM 物件。

我將把重點放在第三個選項,但先讓我們快速瀏覽一下所有選項。

建立 Windows PowerShell Cmdlet

Microsoft 在 Windows PowerShell 中包含了大量 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 函數 ShowMessage 並以參數執行,結果是顯示內含 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

當然啦,要轉換一段可讀取檔案大小的 VBScript,我可以使用 Windows PowerShell cmdlet 來管理磁碟中的物件,但是我想要向您示範從 Windows PowerShell 存取 COM 有多麼簡單。不過要注意的是,我告訴 Windows PowerShell 建立一個 COM 物件,在此處實際建立的物件 $comFileSystemObject,是一個包裝 COM 物件並公開其介面的 .NET 物件。然而這不在本文的討論範圍內,所以無關緊要。

Windows PowerShell 實際示範

看過了如何從 Windows PowerShell 存取 COM 之後,讓我們把焦點轉到群組原則上。此處的範例將顯示較短的程式碼片段,讓您大致了解如何從 Windows PowerShell 使用 GPMC API,不過您可以在與本文相關的程式碼下載中找到一組完整的 Windows PwoerShell 函數來管理群組原則,網址是 technetmagazine.com/code07.aspx[圖 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 的群組原則小組在 GPMC 內附上許多可讓您完整運用的 VBScript 範例,以提高效率。它們位於 %ProgramFiles%\GPMC\Scripts 目錄中,在此目錄中您也可以找到一個稱為 gpmc.chm 的檔案,內含 GPMC API 說明文件。讓我們來看看 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 (或 gm) cmdlet 如何讓您直接從命令列查看物件支援的屬性和方法。當然是跟閱讀說明文件不一樣啦,不過在您忘了確切的參數數目、確切的名稱等等時候,倒是能方便您使用已經熟悉的物件。有個需要注意重點的是,當您查詢 GPMC 說明文件節點清單時,看起來像是 GPM 物件和所有其他類別都是以 I 字母開頭,這是因為 COM 內部運作方式之故,跟我們此處無關;這主要是為了撰寫原生 COM 程式碼的 C++ 程式設計師而設計,表明介面與實作該介面的類別之間的差異。還有在使用 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 報告,但是您顯然可以使用任何一種方法和屬性,像是 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, of the domain 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 轉換指南,這本指南提供一些提示教您如何在 Windows PowerShell 內執行與 VBScript 對等的常見工作。您可以在 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 and CMP Media, LLC. 保留所有權利;未經允許,嚴禁部分或全部複製.