第 4 章 - 單行器和管線

當我第一次開始學習 PowerShell 時,如果我無法使用 PowerShell 單行完成工作,我就會回到 GUI。 經過一段時間,我建立了撰寫腳本、函式和課程模組的技能。 不要讓自己因為因特網上可能看到的一些更進階的範例而不知所措。 沒有人是 PowerShell 的自然專家。 我們都是初學者一次。

我有一些建議,提供那些仍在使用 GUI 進行管理的人員:在系統管理工作站上安裝管理工具,並從遠端管理伺服器。 如此一來,伺服器是否執行操作系統的 GUI 或 Server Core 安裝並不重要。 它將協助您準備使用PowerShell從遠端管理伺服器。

如同先前的章節,請務必遵循 Windows 10 實驗室環境計算機上的指示。

單行

PowerShell 單行器是一個連續管線,不一定是單一實體線路上的命令。 並非所有在一個實體行上的命令都是單行字元。

雖然下列命令位於多個實體行上,但它是 PowerShell 單行程式代碼,因為它是一個連續管線。 它可以寫在一個實體行上,但我選擇在管道符號換行。 管道符號是 PowerShell 中允許自然換行符的其中一個字元。

Get-Service |
  Where-Object CanPauseAndContinue -eq $true |
    Select-Object -Property *
Name                : LanmanWorkstation
RequiredServices    : {NSI, MRxSmb20, Bowser}
CanPauseAndContinue : True
CanShutdown         : False
CanStop             : True
DisplayName         : Workstation
DependentServices   : {SessionEnv, Netlogon, Browser}
MachineName         : .
ServiceName         : LanmanWorkstation
ServicesDependedOn  : {NSI, MRxSmb20, Bowser}
ServiceHandle       : SafeServiceHandle
Status              : Running
ServiceType         : Win32ShareProcess
StartType           : Automatic
Site                :
Container           :

Name                : Netlogon
RequiredServices    : {LanmanWorkstation}
CanPauseAndContinue : True
CanShutdown         : False
CanStop             : True
DisplayName         : Netlogon
DependentServices   : {}
MachineName         : .
ServiceName         : Netlogon
ServicesDependedOn  : {LanmanWorkstation}
ServiceHandle       : SafeServiceHandle
Status              : Running
ServiceType         : Win32ShareProcess
StartType           : Automatic
Site                :
Container           :

Name                : vmicheartbeat
RequiredServices    : {}
CanPauseAndContinue : True
CanShutdown         : False
CanStop             : True
DisplayName         : Hyper-V Heartbeat Service
DependentServices   : {}
MachineName         : .
ServiceName         : vmicheartbeat
ServicesDependedOn  : {}
ServiceHandle       : SafeServiceHandle
Status              : Running
ServiceType         : Win32ShareProcess
StartType           : Manual
Site                :
Container           :

Name                : vmickvpexchange
RequiredServices    : {}
CanPauseAndContinue : True
CanShutdown         : False
CanStop             : True
DisplayName         : Hyper-V Data Exchange Service
DependentServices   : {}
MachineName         : .
ServiceName         : vmickvpexchange
ServicesDependedOn  : {}
ServiceHandle       : SafeServiceHandle
Status              : Running
ServiceType         : Win32ShareProcess
StartType           : Manual
Site                :
Container           :

Name                : vmicrdv
RequiredServices    : {}
CanPauseAndContinue : True
CanShutdown         : False
CanStop             : True
DisplayName         : Hyper-V Remote Desktop Virtualization Service
DependentServices   : {}
MachineName         : .
ServiceName         : vmicrdv
ServicesDependedOn  : {}
ServiceHandle       : SafeServiceHandle
Status              : Running
ServiceType         : Win32ShareProcess
StartType           : Manual
Site                :
Container           :

Name                : vmicshutdown
RequiredServices    : {}
CanPauseAndContinue : True
CanShutdown         : False
CanStop             : True
DisplayName         : Hyper-V Guest Shutdown Service
DependentServices   : {}
MachineName         : .
ServiceName         : vmicshutdown
ServicesDependedOn  : {}
ServiceHandle       : SafeServiceHandle
Status              : Running
ServiceType         : Win32ShareProcess
StartType           : Manual
Site                :
Container           :

Name                : vmictimesync
RequiredServices    : {VmGid}
CanPauseAndContinue : True
CanShutdown         : False
CanStop             : True
DisplayName         : Hyper-V Time Synchronization Service
DependentServices   : {}
MachineName         : .
ServiceName         : vmictimesync
ServicesDependedOn  : {VmGid}
ServiceHandle       : SafeServiceHandle
Status              : Running
ServiceType         : Win32ShareProcess
StartType           : Manual
Site                :
Container           :

Name                : vmicvss
RequiredServices    : {}
CanPauseAndContinue : True
CanShutdown         : False
CanStop             : True
DisplayName         : Hyper-V Volume Shadow Copy Requestor
DependentServices   : {}
MachineName         : .
ServiceName         : vmicvss
ServicesDependedOn  : {}
ServiceHandle       : SafeServiceHandle
Status              : Running
ServiceType         : Win32ShareProcess
StartType           : Manual
Site                :
Container           :

Name                : Winmgmt
RequiredServices    : {RPCSS}
CanPauseAndContinue : True
CanShutdown         : True
CanStop             : True
DisplayName         : Windows Management Instrumentation
DependentServices   : {wscsvc, NcaSvc, iphlpsvc}
MachineName         : .
ServiceName         : Winmgmt
ServicesDependedOn  : {RPCSS}
ServiceHandle       : SafeServiceHandle
Status              : Running
ServiceType         : Win32ShareProcess
StartType           : Automatic
Site                :
Container           :

自然換行符可以發生在常用的字元,包括逗號 (,) 和左括號 ([)、大括弧 ({) 和括號 (()。 其他不太常見的包括分號(;)、等號(=),以及開啟單引號和雙引號(',)。"

使用反引號 (`) 或嚴重強調字元作為行接續字元是一個有爭議的話題。 我的建議是盡量避免。 我通常會在自然換行字元之後立即看到使用反引號撰寫的PowerShell命令。 它沒有理由在那裡。

Get-Service -Name w32time |
>> Select-Object -Property *
Name                : w32time
RequiredServices    : {}
CanPauseAndContinue : False
CanShutdown         : True
CanStop             : True
DisplayName         : Windows Time
DependentServices   : {}
MachineName         : .
ServiceName         : w32time
ServicesDependedOn  : {}
ServiceHandle       : SafeServiceHandle
Status              : Running
ServiceType         : Win32ShareProcess
StartType           : Manual
Site                :
Container           :

上述兩個範例中顯示的命令在PowerShell控制台中正常運作。 但是,如果您嘗試在 PowerShell ISE 的控制檯窗格中執行它們,則會產生錯誤。 PowerShell ISE 的控制檯窗格不會等到下一行輸入命令,就像 PowerShell 控制台一樣。 若要避免 PowerShell ISE 控制檯窗格中發生此問題,請在另一行上繼續命令時,使用 Shift+Enter,而不是按 Enter 鍵。

下一個範例不是PowerShell單行程式,因為它不是一個連續管線。 這是一行上的兩個不同的命令,並以分號分隔。

$Service = 'w32time'; Get-Service -Name $Service
Status   Name               DisplayName
------   ----               -----------
Running  w32time            Windows Time

許多程式設計與腳本語言都需要每一行結尾的分號。 雖然可以在PowerShell中使用這種方式,但不建議這麼做,因為它們不需要。

篩選左方

本章中顯示的命令結果已篩選為子集。 例如,Get-Service與 Name 參數搭配使用,以篩選只傳回至 Windows Time 服務的服務清單。

在管線中,您一律想要將結果篩選到您儘早尋找的結果。 這是使用第一個命令上的參數來完成的,或是最左邊的參數。 這有時稱為 左側篩選。

下列範例使用Get-Service Name 參數,只將結果篩選至 Windows Time 服務。

Get-Service -Name w32time
Status   Name               DisplayName
------   ----               -----------
Running  w32time            Windows Time

查看命令管道傳送至 Where-Object 以執行篩選的範例並不罕見。

Get-Service | Where-Object Name -eq w32time
Status   Name               DisplayName
------   ----               -----------
Running  W32Time            Windows Time

來源的第一個範例會篩選,而且只會傳回 Windows Time 服務的結果。 第二個範例會傳回所有服務,然後將它們傳送至另一個命令來執行篩選。 雖然此範例中看起來可能沒什麼大不了的,但假設您正在查詢 Active Directory 用戶清單。 您真的想要從 Active Directory 傳回數千個使用者帳戶的資訊,只將其傳送至另一個命令,以將他們篩選為小型子集? 我的建議是,即使它似乎無關緊要,也一律篩選左。 您將使用它,如此一來,您就會在真的很重要時自動篩選離開。

我曾經有人告訴我,你指定命令的順序並不重要。 這無法從真相中進一步。 中指定命令的順序在執行篩選時確實很重要。 例如,假設您使用 Select-Object 的案例只選取幾個屬性,並 Where-Object 篩選不在選取範圍中的屬性。 在該案例中,必須先進行篩選,否則當嘗試執行篩選時,屬性就不會存在於管線中。

Get-Service |
Select-Object -Property DisplayName, Running, Status |
Where-Object CanPauseAndContinue

上一個範例中的命令不會傳回任何結果,因為當 的結果Select-Object傳送至 Where-Object時,CanStopAndContinue 屬性不存在。 該特定屬性未「選取」。 從本質上講,它被篩選掉了。反轉和的順序 Select-Object ,併 Where-Object 產生所需的結果。

Get-Service |
Where-Object CanPauseAndContinue |
Select-Object -Property DisplayName, Status
DisplayName                                    Status
-----------                                    ------
Workstation                                    Running
Netlogon                                       Running
Hyper-V Heartbeat Service                      Running
Hyper-V Data Exchange Service                  Running
Hyper-V Remote Desktop Virtualization Service  Running
Hyper-V Guest Shutdown Service                 Running
Hyper-V Time Synchronization Service           Running
Hyper-V Volume Shadow Copy Requestor           Running
Windows Management Instrumentation             Running

管線

如您在此書到目前為止所見的許多範例中所見,許多時候,一個命令的輸出可以做為另一個命令的輸入。 在第 3 章中, Get-Member 用來判斷命令產生的物件類型。 第 3 章也會使用 的 ParameterType 參數 Get-Command 來判斷哪些命令接受該類型的輸入,但不一定是管線輸入。

視命令說明的徹底程度而定,它可能包含 INPUTSOUTPUTS段。

help Stop-Service -Full
...
INPUTS
    System.ServiceProcess.ServiceController, System.String
        You can pipe a service object or a string that contains the name of a service
        to this cmdlet.

OUTPUTS
    None, System.ServiceProcess.ServiceController
        This cmdlet generates a System.ServiceProcess.ServiceController object that
        represents the service, if you use the PassThru parameter. Otherwise, this
        cmdlet does not generate any output.
...

只有說明的相關區段會顯示在先前的結果中。 如您所見, INPUTS 區段指出 ServiceControllerString 物件可以管線傳送至 Stop-Service Cmdlet。 它不會告訴您哪些參數接受該類型的輸入。 判斷資訊的最簡單方式之一,就是在 Cmdlet 的完整說明 Stop-Service 版本中查看不同的參數。

help Stop-Service -Full
...
-DisplayName <String[]>
    Specifies the display names of the services to stop. Wildcard characters are
    permitted.

    Required?                    true
    Position?                    named
    Default value                None
    Accept pipeline input?       False
    Accept wildcard characters?  false

-InputObject <ServiceController[]>
    Specifies ServiceController objects that represent the services to stop. Enter a
    variable that contains the objects, or type a command or expression that gets the
    objects.

    Required?                    true
    Position?                    0
    Default value                None
    Accept pipeline input?       True (ByValue)
    Accept wildcard characters?  false

-Name <String[]>
    Specifies the service names of the services to stop. Wildcard characters are
    permitted.

    Required?                    true
    Position?                    0
    Default value                None
    Accept pipeline input?       True (ByPropertyName, ByValue)
    Accept wildcard characters?  false
...

同樣地,我只會在先前的結果集中顯示說明的相關部分。 請注意,DisplayName 參數不接受管線輸入,InputObject 參數會接受 ServiceController 物件的值輸入管線輸入,而 Name 參數接受字元串物件的管線輸入。 它也會依屬性名稱接受管線輸入

當參數同時接受屬性名稱和傳值輸入的管線時,它一律會先嘗試 傳值 。 如果依值失敗,則會依屬性名稱嘗試根據價值 ,有點誤導。 我偏好依類型呼叫它。 這表示如果您以管線將產生 ServiceController 物件類型的命令結果傳送至 Stop-Service,它會將輸入系結至 InputObject 參數。 但是,如果您使用管線將產生 String 輸出的命令結果傳送至 Stop-Service,它會將它系結至 Name 參數。 如果您使用管線將不會產生 ServiceController 或 String 物件的命令結果傳送至 Stop-Service,但它會產生包含名為 Name 之屬性的輸出,則會將 Name 屬性從輸出系結至 的 Stop-ServiceName 參數。

判斷命令產生的輸出 Get-Service 類型。

Get-Service -Name w32time | Get-Member
   TypeName: System.ServiceProcess.ServiceController

Get-Service 會產生 ServiceController 物件類型。

如您先前在說明中所見,Stop-Service InputObject 參數會透過管線依值接受 ServiceController 物件(依類型)。 這表示當 Cmdlet 的結果Get-Service傳送至 Stop-Service時,它們會系結至的 Stop-ServiceInputObject 參數。

Get-Service -Name w32time | Stop-Service

現在嘗試字串輸入。 使用管道 w32timeGet-Member 確認它是字串。

'w32time' | Get-Member
   TypeName: System.String

如先前的說明所示,使用管線將字串Stop-Service依值系結Stop-ServiceName 參數。 將管線 w32time 傳送至 來 Stop-Service測試此專案。

'w32time' | Stop-Service

請注意,在上一個範例中,我在字串 w32time周圍使用了單引號。 在 PowerShell 中,您應該一律使用單引號,而不是雙引號,除非引號字串的內容包含必須擴充至其實際值的變數。 藉由使用單引號,PowerShell 不需要剖析引號內所包含的內容,讓您的程式代碼執行速度會更快。

建立自定義物件,以依 Name 參數Stop-Service的屬性名稱測試管線輸入。

$CustomObject = [pscustomobject]@{
 Name = 'w32time'
 }

CustomObject 變數的內容PSCustomObject 物件類型,其中包含名為 Name 的屬性。

$CustomObject | Get-Member
   TypeName: System.Management.Automation.PSCustomObject

Name        MemberType   Definition
----        ----------   ----------
Equals      Method       bool Equals(System.Object obj)
GetHashCode Method       int GetHashCode()
GetType     Method       type GetType()
ToString    Method       string ToString()
Name        NoteProperty string Name=w32time

如果您要以 $CustomObject 引弧括住變數,則想要使用雙引號。 否則,使用單引號,常值字串 $CustomObject 會傳送至 , Get-Member 而不是變數所包含的值。

雖然將 的內容$CustomObject管線傳送至 Cmdlet 會系結至 Stop-ServiceName 參數,但這次它會依屬性名稱系結,而不是依值系結,因為 的內容$CustomObject是具有名為 Name 屬性的物件。

在此範例中,我使用不同的屬性名稱建立另一個自定義物件,例如 Service

$CustomObject = [pscustomobject]@{
  Service = 'w32time'
}

嘗試使用管道$CustomObject傳送至 Stop-Service 時,會產生錯誤,因為它不會產生 ServiceControllerString 物件,而且沒有名稱為 Name 的屬性。

$CustomObject | Stop-Service
Stop-Service : Cannot find any service with service name '@{Service=w32time}'.
At line:1 char:17
+ $CustomObject | Stop-Service
+
    + CategoryInfo          : ObjectNotFound: (@{Service=w32time}:String) [Stop-Service]
   , ServiceCommandException
    + FullyQualifiedErrorId : NoServiceFoundForGivenName,Microsoft.PowerShell.Commands.S
   topServiceCommand

如果某個命令的輸出未與另一個命令的管線輸入選項一致, Select-Object 則可以用來重新命名屬性,讓屬性的行正確。

$CustomObject |
  Select-Object -Property @{name='Name';expression={$_.Service}} |
    Stop-Service

在此範例中, Select-Object 用來將 Service 屬性重新命名為名為 Name 的屬性。

此範例的語法一開始可能有點複雜。 我學到的是,您永遠不會透過複製和貼上程式代碼來學習語法。 花時間在 中輸入程序代碼。 幾次之後,它就成了第二個天性。 擁有多個監視器是一項巨大的好處,因為您可以在一個畫面上顯示範例程序代碼,並在另一個畫面上輸入。

有時候,您可能想要使用不接受管線輸入的參數。 下列範例示範如何使用某個命令的輸出做為另一個命令的輸入。 首先,將幾個 Windows 服務的顯示名稱儲存到文字檔中。

'Background Intelligent Transfer Service', 'Windows Time' |
Out-File -FilePath $env:TEMP\services.txt

您可以執行命令,在括弧內提供所需的輸出,做為需要輸入之命令參數的值。

Stop-Service -DisplayName (Get-Content -Path $env:TEMP\services.txt)

這就像代數中的作業順序一樣,對於那些記得其運作方式的人來說。 括弧內的命令一律會在命令的外部部分之前執行。

PowerShellGet

PowerShellGet 是一個 PowerShell 模組,其中包含用來探索、安裝、發佈和更新 PowerShell 模組(以及其他成品)的命令,或從 NuGet 存放庫進行更新。 PowerShellGet 隨附 PowerShell 5.0 版和更新版本。 它可作為PowerShell 3.0版和更新版本的個別下載。

Microsoft 會裝載名為 PowerShell 資源庫 的在線 NuGet 存放庫。 雖然此存放庫是由 Microsoft 所裝載,但 Microsoft 不會撰寫存放庫中所包含的大部分模組。 從 PowerShell 資源庫 取得的任何程序代碼,都應該在隔離的測試環境中徹底檢閱,然後再被視為適合在生產環境中使用。

大部分公司都會想要裝載自己的內部私人 NuGet 存放庫,讓他們可以只張貼內部使用模組,以及他們從其他來源下載的模組,一旦驗證它們為非惡意。

Find-Module使用屬於 PowerShellGet 模組一部分的 Cmdlet,在我撰寫名為 MrToolkit 的 PowerShell 資源庫 中找到模組。

Find-Module -Name MrToolkit
NuGet provider is required to continue
PowerShellGet requires NuGet provider version '2.8.5.201' or newer to interact with
NuGet-based repositories. The NuGet provider must be available in 'C:\Program
Files\PackageManagement\ProviderAssemblies' or
'C:\Users\MrAdmin\AppData\Local\PackageManagement\ProviderAssemblies'. You can also
install the NuGet provider by running 'Install-PackageProvider -Name NuGet
-MinimumVersion 2.8.5.201 -Force'. Do you want PowerShellGet to install and import the
NuGet provider now?
[Y] Yes  [N] No  [S] Suspend  [?] Help (default is "Y"):

Version    Name                                Repository           Description
-------    ----                                ----------           -----------
1.1        MrToolkit                           PSGallery            Misc PowerShell Tools

第一次使用 PowerShellGet 模組的其中一個命令時,系統會提示您安裝 NuGet 提供者。

若要安裝 MrToolkit 模組,請將上一個命令管線傳送至 Install-Module

Find-Module -Name MrToolkit | Install-Module
Untrusted repository
You are installing the modules from an untrusted repository. If you trust this
repository, change its InstallationPolicy value by running the Set-PSRepository cmdlet.
Are you sure you want to install the modules from
'https://www.powershellgallery.com/api/v2/'?
[Y] Yes  [A] Yes to All  [N] No  [L] No to All  [S] Suspend  [?] Help (default is "N"): y

由於 PowerShell 資源庫 是不受信任的存放庫,因此會提示您核准模組的安裝。

尋找管線輸入的簡單方式

MrToolkit 模組包含名為 Get-MrPipelineInput的函式。 此 Cmdlet 可用來輕鬆地判斷命令接受管線輸入的參數、接受的物件類型,以及它們是否接受依值或屬性名稱輸入管線

Get-MrPipelineInput -Name Stop-Service
ParameterName ParameterType                             ValueFromPipeline ValueFromPipelineByPropertyName
------------- -------------                             ----------------- ---------------
InputObject   System.ServiceProcess.ServiceController[]              True           False
Name          System.String[]                                        True            True

如您所見,我們先前透過篩選說明所決定的相同資訊,可以輕鬆地透過此函式來判斷。

摘要

在本章中,您已瞭解 PowerShell 單行程序代碼。 您已瞭解命令開啟的實體行數與 PowerShell 單行是否無關。 您也已了解篩選左側、管線和PowerShellGet。

檢閱

  1. 什麼是 PowerShell 單行程式代碼?
  2. PowerShell 中可以發生自然換行符的部分字元有哪些?
  3. 您為什麼要左篩選?
  4. PowerShell 命令可以接受管線輸入的兩種方式為何?
  5. 為什麼您不應該信任 PowerShell 資源庫 中找到的命令?