Windows PowerShell構建更完善的清單工具

Don Jones

目錄

物件總是相同
靈活的輸出
靈活的輸入
如何處理錯誤?
提高函數的可用性

在此專欄的上一期中,我創建了一個可以從多台遠端電腦檢索 Service Pack 清單資訊的函數。這是個非常方便且實用的工具,但我認為用於開發這個工具的過程也很重要。在本期專欄中,我想演示我採用的過程,以便您能夠更好地掌握構建自己的函數所需的技能。

我在上期專欄中所創建的函數如下所示:

Function Get-SPInventory {
  PROCESS {
    $wmi = Get-WmiObject Win32_OperatingSystem  
      –comp $_ | 
    Select CSName,BuildNumber,
      ServicePackMajorVersion
    Write-Output $wmi
  }
}

以下是它的用法:

Get-Content c:\computernames.txt | 
Get-SPInventory

如果文字檔每行包含一個電腦名稱,則此用法非常有效。

這個函數有一個明顯的漏洞,就是只能從單個 WMI(Windows 管理規範)類返回資料。那麼,如果我希望返回 BIOS 序號怎麼辦?

物件總是相同

問題在於我的函數是從 WMI 檢索 Win32_OperatingSystem 類,並且只輸出該物件。Win32_OperatingSystem 類與所有物件一樣,所包含的資料是固定的 — 它不包含 BIOS 序號,而且永遠無法包含此類資訊。如果我只是單純輸出 Win32_OperatingSystem 物件,我的輸出永遠無法包含序號。

無論您在 WMI 中怎麼檢查,都找不到同時包含 BIOS 序號和 Service Pack 資訊的物件類。因此,我的函數不能只輸出一個 WMI 類。實際上,我需要該函數輸出我即時構建的自訂物件 — 也就是包含我需要的所有資料的物件。

要創建不含任何屬性的新空白物件,我只需運行以下代碼:

$obj = New-Object PSObject

新物件存儲在變數 $obj 中,而且我可以在其中隨意添加任何資料。 添加完我所有的資料後,它隨即成為我函數的輸出。

在我的函數內,我檢索了 Win32_OperatingSystem 資訊,並把它存儲在變數 $wmi 中。 使用此資訊就像引用 $wmi 的屬性那樣簡單。 例如,若要獲得 BuildNumber 屬性,可以使用以下代碼:

$wmi.BuildNumber

若要將屬性添加到我的自訂物件中,我必須將物件(在變數 $obj 中)輸送到 Add-Member。 我告知 Add-Member 希望添加的屬性類型(永遠都是 NoteProperty)、希望添加的屬性名稱,以及希望屬性具備的值,如下所示:

$obj | Add-Member NoteProperty BuildNumber 
($wmi.BuildNumber)

我也可以將相同方法用於電腦系統名稱和 Service Pack 版本:

$obj | Add-Member NotePropertyCSName 
  ($wmi.CSName)
$obj | Add-Member NotePropertySPVersion 
  ($wmi.ServicePackMajorVersion)

將以上內容整合在一起便構成了一個新函數(參見圖 1)。 請注意,我更改了 Write-Output 行,以輸出自訂物件,而不是輸出原始的 $wmi 物件。

圖 1 一個自訂物件

Function Get-SPInventory {
  PROCESS {
    $wmi = Get-WmiObject Win32_       OperatingSystem –comp $_ | 
       Select CSName,BuildNumber,ServicePack         MajorVersion
    $obj = New-Object PSObject
    $obj | Add-Member NoteProperty 
      BuildNumber ($wmi.BuildNumber)
    $obj | Add-Member NoteProperty CSName      ($wmi.CSName) 
    $obj | Add-Member NoteProperty SPVersion 
      ($wmi.ServicePackMajorVersion)
    Write-Output $obj
  }
}

現在我需要獲取 BIOS 序號。 經過一番搜索,可以找到 Win32_BIOS 類,它具有 Serial­Number 屬性(圖 2 顯示了連線文檔頁的一部分)。 所以我只需查詢 Win32_BIOS 類,將 Serial­Number 屬性添加到我的自訂物件中,就可以了。 您可以在圖 3 中看到修訂後的函數。

fig02.gif

圖 2 Win32_BIOS 類文檔頁(按一下可獲得大圖)

圖 3 包含 SerialNumber 屬性

Function Get-SPInventory {
  PROCESS {
    $wmi = Get-WmiObject Win32_      OperatingSystem –comp $_ |    
      Select CSName,BuildNumber,ServicePack        MajorVersion
    $obj = New-Object PSObject
    $obj | Add-Member NoteProperty 
      BuildNumber ($wmi.BuildNumber)
    $obj | Add-Member NoteProperty CSName      ($wmi.CSName)
    $obj | Add-Member NoteProperty SPVersion 
      ($wmi.ServicePackMajorVersion)

    $wmi = Get-WmiObject Win32_BIOS –comp $_ | 
      Select SerialNumber
    $obj | Add-Member NoteProperty BIOSSerial 
      ($wmi.SerialNumber)
    Write-Output $obj
  }
}

靈活的輸出

通過將自訂物件用於輸出,我為這個函數創造了各種可能的用途。 例如,若要值包含 Windows Server 2008 電腦,我可以使用以下代碼:

Gc c:\computernames.txt | Get-SPInventory | 
Where { $_.BuildNumber –eq 6001 }

或者,若要創建一個 HTML 檔以列出所有未安裝 Service Pack 1 的 Windows Vista 電腦,我可以使用以下代碼:

Gc c:\computernames.txt | Get-SPInventory | 
Where { $_.BuildNumber –eq 6000 } | 
ConvertTo-HTML | Out-File c:\VistaInventory.html

可能的用途無窮無盡 — XML、CSV、表、清單、HTML、排序、篩選、分組等等。 Windows PowerShell 中的內置 cmdlet 可與物件協同工作。 通過創建用於輸出的物件,您可以利用 Windows PowerShell 的所有功能,不用多費一絲力氣!

靈活的輸入

可惜的是,就輸入而言,我的函數就沒那麼靈活了。 產生此缺陷的部分原因是在 Windows PowerShell 版本 1 中設計函數的限制 — 版本 2 將引入腳本 cmdlet,這樣將會大大地提高靈活性。

預設情況下,我的函數希望其管道輸入是簡單的字串物件。 舉例來講,如果我以 cmdlet 取代 Get-Content 命令,從 Active Directory 檢索所有電腦名稱,則這個函數無法運行,如下所示:

Get-QADComputer | Get-SPInventory

在這種情況下,Get-QADComputer 命令(免費 Active Directory 管理 cmdlet 組的一部分,您可以從 quest.com/powershell 獲得該組)將返回具有 Name 屬性的物件,而不是返回簡單的字串物件。 為使該命令發揮作用,我得修改一下函數(如圖 4 所示),您要特別注意以紅色突出顯示的更改。

圖 4 最終結果

Function Get-SPInventory {
  PROCESS {
    $wmi = Get-WmiObject Win32_OperatingSystem –<span class="clsRed" xmlns="http://www.w3.org/1999/xhtml">comp 
    $_.Name</span> | Select 
      CSName,BuildNumber,
        ServicePackMajorVersion
    $obj = New-Object PSObject
    $obj | Add-Member NoteProperty 
      BuildNumber ($wmi.BuildNumber)
    $obj | Add-Member NoteProperty CSName 
      ($wmi.CSName)
    $obj | Add-Member NoteProperty SPVersion  
      ($wmi.ServicePackMajorVersion)

    $wmi = Get-WmiObject Win32_BIOS –comp <span class="clsRed" xmlns="http://www.w3.org/1999/xhtml">$_.Name |</span> 
      Select SerialNumber
    $obj | Add-Member NoteProperty BIOSSerial 
      ($wmi.SerialNumber)
    Write-Output $obj
  }
}

我的函數是對管道物件的 Name 屬性執行操作,而不是將整個管道物件傳送到 –computerName 參數。 就靈活性而言,此微小更改相當有價值 — 通過使用 Get-QADComputer,我可以對輸入設置限制,例如限制為特定組織單位中的電腦,或是僅限於基於 Active Directory 屬性符合其他條件的那些電腦。

如何處理錯誤?

此函數最終不可避免地會遇到一些它無法連接或無權連接之類的電腦。 當前編寫的函數會在 Windows PowerShell 主控台視窗中顯示一條以紅色文本表示的錯誤消息,然後會繼續對下一台電腦執行操作。

當然,這可能正是您所希望的結果,您也可能傾向于使該函數禁止出現這些錯誤消息。 這很容易實現,只需將下麵一行添加到函數的 PROCESS 腳本塊的開頭:

$ErrorActionPreference = "SilentlyContinue"

不過,您也可能希望採用比較複雜的方法,創建錯誤日誌來列出無法連接的電腦名稱。 Windows PowerShell 同樣可以辦到,但是您必須等到下個月才能知道怎麼做!

本月 Cmdlet:Export-Alias 和 Import-Alias

下麵提供了共用您已創建的自訂別名或在 shell 每次啟動時輕鬆載入自訂別名的絕佳方法。 創建您需要的所有別名後,將它們匯出到一個檔,如下所示:

Export-Alias c:\aliases.xml

然後,若要將這些別名載入回 shell 中,請運行以下命令:

Import-Alias c:\aliases.xml

您可以把第二個命令置於您的 Windows PowerShell 設定檔腳本中,使之在 shell 每次啟動時運行。 另外,您也可以把這個檔置於網路共用上,以便與您共事的其他管理員隨時使用。

提高函數的可用性

到目前為止,您可能已經把此函數鍵入到 .ps1 檔中,並運行了腳本。 這都沒什麼問題,但在可用性方面有幾個問題。 其中一個問題是每次需要使用函數時,都必須打開那個指令檔,修改調用函數的那一行(以添加任何輸出、排序,或是您當時所需的其他命令),保存該腳本,然後運行它。 如果可直接從 shell 的主控台視窗內使用函數本身的話,則會簡單很多,幾乎像一條 cmdlet 一樣簡單。

要實現此任務,有兩種選擇。 第一個選擇是直接將函數複製到 Windows PowerShell 設定檔腳本中,該腳本是 shell 在每次啟動時會自動運行的四個指令檔之一(如果存在)。 隨 Windows PowerShell 一同安裝的“快速入門指南”列出了這四個位置。

我通常是使用名為 profile.ps1 的檔,它必須位於您的“文檔”資料夾(在 Windows XP 和 Windows Server 2003 電腦上為“我的文件”資料夾)內名為“Windows­PowerShell”(無空格)的資料夾中。 將函數置於配置文檔後,每次進入 shell,都可以直接從命令提示符使用它。

第二個選擇是將函數(只有函數,不含任何其他代碼)包含在腳本中,然後將該腳本以“點源”(dot source) 方式置入 shell 中。 當您運行腳本時,Windows PowerShell 通常都會為該腳本創建新作用域。 該腳本內發生的任何操作都會在該作用域內進行,例如函數的定義。 當腳本結束運行時,該作用域會被棄用,發生在該作用域內的一切操作隨之丟失。

因此,請記住,如果您擁有只包含 Get-SP­Inventory 函數的指令檔,那麼運行該腳本將會創建新作用域、定義該函數,然後棄用該作用域,該函數會隨之消失。 顯然,這對您來說不太實用。

相反,通過 dot source 運行腳本不會創建新作用域。 如果我把 Get-SPInventory 置於名為 MyFunction.ps1 的檔中,我可以以如下所示方式 dot source 它:

PS C:\> . c:\functions\myfunction

請注意腳本的路徑和檔案名前面的單個句點後面有個空格。 這會告知 shell 在當前作用域中運行腳本,這意味著發生在腳本中的一切操作在腳本結束運行後會保存下來。 結果 Get-SPInventory 腳本現在已定義在 shell 中,而您現在可以直接從命令列使用它,幾乎像使用 cmdlet 一樣。

Don Jones 是 Concentrated Technology 的創始人之一,同時也是眾多 IT 書籍的作者。 您可以通過以下網址閱讀他的 Windows PowerShell 每週提示或與他取得聯繫: ConcentratedTech.com