$_ und die Verwendung von Where-Object

Richard Siddaway hat kürzlich einen Blog zu dem Thema veröffentlicht, den ich hier gerne nachvollziehen möchte. $_ und Where-Object werden einfach zu häufig benutzt, ohne das nötige Hintergrundwissen zu haben. Deshalb seht ein paar Beispiele und die Erklärung unten.

$_ ist das aktuelle Pipeline Objekt. Man kann auch $PSitem verwenden, jedoch ist $_ viel gebräuchlicher. Wenn also der vorherige Befehl (z.B. Get-Service) mehrere Objekte ausgibt dann ist $_ immer das aktuelle Objekt, eines nach dem anderen.

Wenn also das Ergebnis eines Befehls nachträglich gefiltert werden soll kann das so aussehen wie unten

[codesyntax lang=“powershell“]

# Logische Schreibweise die auch erklärt was passiert:
Get-Service | Where-Object -FilterScript {$_.Name -like 'a*'}
# bekanntere Schreibweise
Get-Service | Where-Object {$_.Name -like 'a*'}

[/codesyntax]

Ab PowerShell 3.0 gibt es eine einfacher Variante

[codesyntax lang=“powershell“]

Get-Service | Where Name -like 'a*'
#was eine verkürzte Version des Befehls unten darstellt.
Get-Service | Where -Property Name -like -Value 'a*'

[/codesyntax]

Das ist effizient, hat aber den Haken, dass keine logischen Verknüpfungen (-and –or) verwendet werden können. Wenn also mehrere Filter angewendet werden sollen muss man zur “alten” Schreibweise greifen. Dies funktioniert mit und ohne runde Klammern.

[codesyntax lang=“powershell“]

Get-Service | Where-Object {($_.Name -like 'a*') -and ($_.Status -eq 'Stopped')}
Get-Service | Where-Object {$_.Name -like 'a*' -and $_.Status -eq 'Stopped'}

[/codesyntax]

$_ kann man natürlich auch mit Foreach verwendet. Das Beispiel unten liest die Netzwerkadapter mittels CIM-Cmdlets aus, speichert bestimmte Eigenschaften des CIM Objektes in Variablen und in Folge in einer HashTabelle. Zum Schluss wird das Ganze dann in einem PSObject abgelegt und ausgegeben.

[codesyntax lang=“powershell“]

Get-CimInstance -ClassName Win32_NetworkAdapter -Filter "NetEnabled = $true" | 
ForEach-Object {
$ip = Get-CimInstance -ClassName Win32_NetworkAdapterConfiguration -Filter "Index = $($_.DeviceId)"
$props = @{
Name = $_.NetConnectionId
Product = $_.ProductName
DHCP = $ip.DHCPEnabled
IP   = $ip.IPAddress
}
New-Object -TypeName PSObject -Property $props
}

[/codesyntax]

Unten bringt Rick noch ein Beispiel mit Berechnungen der Diskgröße, welches wir hier mittels $PSItem statt $_ verwenden nur um zu zeigen das es egal ist welche Variable ihr verwendet.

[codesyntax lang=“powershell“]

Get-CimInstance -ClassName Win32_LogicalDisk -Filter "DriveType=3" |
Select-Object DeviceId, VolumeName,
@{N='Size(GB)'; E={[math]::Round($PSItem.Size / 1GB, 2)}},
@{N='Used(GB)'; E={[math]::Round(($PSItem.Size - $_.FreeSpace) / 1GB, 2)}},
@{N='Free(%)'; E={[math]::Round(($PSItem.FreeSpace / $_.Size) * 100, 2 )}}

[/codesyntax]

$PSItem.Site und $_FreeSpace werden hier gemischt, funktionieren also auch “gemischt”

Was nicht funktioniert, ist die Verwendung von $_ in Foreach Schleifen. Den genauen Unterschied zwischen foreach und foreach-object kann man hier nachlesen). Also, der Code unten wird nicht funktionieren.

[codesyntax lang=“powershell“]

$nics = Get-CimInstance -ClassName Win32_NetworkAdapter -Filter "NetEnabled = $true"
$data = foreach ($nic in $nics) {
$ip = Get-CimInstance -ClassName Win32_NetworkAdapterConfiguration -Filter "Index = $($_.DeviceId)"
$props = @{
Name = $_.NetConnectionId
Product = $_.ProductName
DHCP = $ip.DHCPEnabled
IP   = $ip.IPAddress
}
New-Object -TypeName PSObject -Property $props
}
$data |Format-List

[/codesyntax]

So, und nun viel Spass beim ausprobieren !

R.

Share This Post