PowerShell Variablen

Wie in jeder guten Programmiersprachen gibt es Variablen, die gesetzt und gelesen werden können. In PowerShell beginnen alle Variablen immer mit einem "$" als Präfix.

Vordefinierte Variablen

Neben selbst eingerichteten Variablen gibt es eine ganze Menge vorbelegte Variablen der PowerShell:

Variable Description

$_

Das aktuelle Objekte einer Pipeline, eines Filter und den verschiedenen Schleifen.
ACHTUNG: In einer "Try/Catch"-Schleife enthält die Variable im Catch-Block den Fehler und nicht mehr die Daten in der Schleife

$$

Enthält das letzte Token der Shell-Eingabe

$?

Enthält den Erfolg/Fehler des letzten Befehls

$Args

Enthält übergebene Parameter einer Funktion bzw. eines Skripts

$Error

Enthält die bis zu 256 letzten Fehlermeldungen

$HOME

Heimatverzeichnis des Anwenders (%HOMEDRIVE%\%HOMEPATH%)

$Input

Input pipe einer Funktion oder eines Codeblocks

$Match

Eine Hashtabelle, die alle Elemente enthält, welche durch den "-match"-Operator gefunden wurde.

$MyInvocation

Informationen über das aktuelle Skript oder die Kommandozeile

$Host

Informationen über den aktuellen Computer

$LastExitCode

Exitcode der zuletzt aufgerufenen Anwendung (Nicht PowerShell -Befehl)

$true

Boolean WAHR

$false

Boolean FALSCH

$null

"nichts", also ein NULL-Objekt.  Ideal um Objekte zu zerstören oder in IF-Abfragen zu vergleichen

$OFS

"Output Field Separator". Hiermit geben Sie das Trennzeichen für die Konvertierung von Arrays zu String und umgekehrt an. Standardeinstellung ist das "Leerzeichen"

$ShellID

Identifizierung die Shell. Normalweise "Microsoft.PowerShell"

$StackTrace

Stacktrace der letzten Aktion

$env:variablenname

Umgebungsvariablen abfragen

$global:varname

Variablen "global" setzen und lesen

Aber auch die selbst erstellten Variablen können (und sollten) entsprechend typisiert werden. So lassen sich Flüchtigkeitsfehler bei der Programmierung vermeiden, wenn z.B. Strings per Addition "aneinandergehängt" werden und sie eigentlich eine numerische Addition erwartet hätten.

Scope

Variablen aber auch Funktionen haben einen Gültigkeitsbereich. Sie sind per Default immer nur im eigenen Bereich und Unterbereichen gültig, Sie können Variablen aber auch als "Privat" deklarieren und damit den Zugriff durch Unterprogramme zu verbieten. Umgekehrt können Variablen und Funktionen auch "Global" definiert werden. Global bezieht sich auf die aktuelle PowerShell. Zwischen unterschiedlichen PowerShell-Umgebungen gibt es keinen durchgriff.

PowerShell Typen

Es gibt eine ganze Menge von Typen, Genau genommen kann jede .NET Typisierung auch in PowerShell übernommen werden. Hier eine allgemeine Liste:

Type Description

[int]

32-bit signed integer

[long]

64-bit signed integer

[string]

Fixed-length string of unicode characters

[char]

A unicode 16-bit character

[byte]

An 8-bit unsigned character

[bool]

Boolean True/False value

[decimal]

An 128-bit decimal value

[single]

Single-precision 32-bit floating point number

[double]

Double-precision 64-bit floating point number

[xml]

Xml object

[array]

An array of values

[hashtable]

Hashtable object

Die Wahl des "richtigen" Typs ist schon wichtig. Mit "Nummern" in Strings kann man nicht rechnen und erst mit Kommazahlen rechnet, kann auch lustige Dinge erleben. Hier ein paar Beispiele:

Eingabe Ergebnis

123 * 0.03

7,02

[float]234* [float]0.03

7,01999984309077

[single]"234" * [float]0.03

7,01999984309077

[single]234 * [single]0.03

7,01999984309077

[double]234 * [double]0.03

7,02

PowerShell kann also schon mal den richtigen Type konvertieren, aber wenn Sie eine Variable "falsch" deklariert haben, dann kommen unschöne Rundungsfehler zum Vorschein.

PowerShell und Aufzählungen (Enumeration)

An vielen Stellen werden Variablen übergeben. Anders als bei VBScript, wo es nur wenige Basistypen (Integer, String und die universellen Variants etc.) gibt, kann man in .NET mit Aufzählungen arbeiten, die nur bestimmte Werte annehmen können. Eine Funktion kann damit streng prüfen, ob die Parameter überhaupt "geeignet" sind. Also Administrator bin ich an verschiedenen Stellen daher eingeschränkt und kann nicht einfach einen String übergeben, wenn eigentlich ein Wert einer Aufzählung erforderlich ist. Also muss ich heraus bekommen, welche Inhalte eine Aufzählung hat:

[System.Enum]::GetValues([System.Diagnostics.EventLogEntryType])

# oder
[system.text.encoding] | gm -static -Type Property | Select -Expand Name

In der PowerShell sieht das dann wie folgt aus:

Diese einfache Abfrage funktioniert leider nur mit einfachen Aufzählungen. Komplexere Typen wie z.B. [Microsoft.Exchange.Data.Storage.Management.ExTimeZoneValue" können so nicht aufgelistet werden

Sie können natürlich auch weiter mit der MSDN die Klassen und Aufzählungen suchen und betrachten.

Ganz hilfreich ist auch die Funktion, eigene Variablentypen zu definieren, die dann nur die vorher festgelegten Inhalte annehmen können. Ich habe in einem Skript die Aufgabe einen "Status" zu pflegen und ein einfacher "String" ist da doch fehleranfällig und ein numerischer Wert nicht aussagekräftig genug.

$enum = " 
   namespace msxfaq {
      public enum sipstate {
         outside=1,
         1stin=2,
         1stout=3,
         sipout=4,
         sipin=5
      }
   } 
"
Add-Type -TypeDefinition $enum -Language CSharpVersion3

Hashtables

Eine sehr leistungsfähige Funktion sind so genannte "Hash-Tabellen" oder unter VBScript als Dictionary-Objekt bezeichnet. Dieser Datenspeicher besteht aus mehreren Informationen, die durch einen eindeutigen, einmaligen Schlüssel adressierbar sind. Ich nutze diese sehr gerne um Informationen für eine spätere Verwendung zu puffern.

[hashtable]$hash1=@{}
$hash.add("key1","value1")
$hash.add("key2","value2")
write-host "Anzahl" $hash.count
Write-host "Keys" $hash.keys
Write-Host "Value of Key1:" $hash.item("key1")

Wenn ich z.B. Informationen über die Postfachgröße einer Datenbank auslese und später beim Durchlaufen der Benutzer diese Information abrufen will, dann lege ich diese am Anfang mit z.B. dem Namen der Mailbox als Schlüssel ab. Der Wert kann dabei ein beliebiger Typ sein. Sogar komplexe Objekte sind möglich. Eine einfache Operation ist die Prüfung, ob ein Schlüssel oder ein Wert vorhanden ist:

if ($hash.containskey("key1")) {write-host "Key vorhanden"}

if ($hash.containsvalue("value1")) {write-host "Value vorhanden"}

Diese Funktionen sind auch wichtig, um vor dem Addieren zu prüfen, ob es den Wert schon gibt. Dies ist aber nicht erforderlich, wenn man die Hashtable vergleichbar einem Array anspricht bei dem der Key nur nicht numerisch sein muss:

# Wert setzen
$hash["key1"]=""value1"
# Wert setzen oder überschreiben ohne Warnung
$hash["key1"]=""value2"

Allerdings können Sie eine Hashtabelle nicht in einer FOR-Schleife verändern. Folgendes führt also zu dem Fehler: "Collection was modified; enumeration operation may not execute."

[hashtable]$hash=@{}
$hash.add("A","1")
$hash["B"]="2"
foreach ($key in $hash.keys) {
   $hash.item($key)="neu"
}

Das funktioniert nicht, da $key dann eine referenzierte Variable ist. Sie können sich aber helfen, indem Sie entweder eine Kopie der Hashtable nutzen oder die Aufzählung in ein Array kopieren.

# Aufzaehlung als Array
foreach ($key in @($hash.keys)) {
   $hash.item($key)="neu"
}

# Nutzen einer Kopie (Clonen)

foreach ($key in ($hash.clone()).keys) {
   $hash.item($key)="neu"
}

Die Konvertierung der Keys einer Hashtabelle mit 100.000 Keys in ein Array ist mit 30ms zwar deutlich schneller als ein "Clone" (ca. 130ms) aber nicht wirklich signifikant.

Klassisch ist eine Hashtable ja auch nur eine "Tabelle" mit einem Key und einem Value. Wenn beide ein einfacher Typ wie z.B. String sind, dann kann man sich ja schon fragen, warum eine Hashtabelle nicht einfach als CSV-Datei mit zwei Spalten exportiert und importiert werden kann. Ein $hashtabelle | export-csv Dateiname liefert zumindest nicht passendes. Aber es geht doch.

#Hashtabelle initialisieren
[hashtable]$hashtable=@{}
foreach ($count in (1..10000)) {$hashtable.add($count,("wert:$count"))}

# Export als CSV. mit dem Select verhindere ich die doppelte Ausgabe des keys
$hashtable.GetEnumerator() | select key,value | Export-Csv -NoTypeInfor ation .\hash.csv 

# Der Import ist aber etwas kniffliger. Entweder per For-Schleife oder eingebaute Convert-Funktion
# Hier erst die Loop
[hashtable]$hashtable=@{}
import-csv .\hash.csv | %{$hashtable.add($_.key,$_.value)}

# Alternativ per convertfrom-stringdata
$hashtable = (get-content .\hash.csv | select -skip 1).replace(",","=").replace("""","") | convertfrom-stringdata

Der Trick mit dem Export über den "GetEnumerator()" ist noch ganz hilfreich, wenn es sich wirklich um triviale Typen handelt, die als String ausgegeben werden können. Der Import über Convertfrom-Stringdata macht aus meiner Sicht aber gar keinen Sinn, da keine Hashtable sondern ein Arrays von kleinen Hashtables zurück gegeben wird, was aber nicht sofort ersichtlich ist.

Arrays

Oft muss man sich zwischen Arrays und Hashtables entscheiden. Eine Hashtable bietet natürlich einen schnellen direkten Zugriff auf ein individuelles Element, wenn man den "Key" dazu nutzt. Dieser muss dann aber auch eindeutig sein. Ein Arrays ist eine Liste, in der Elemente auch mehrfach vorkommen können. Sobald ein Array dann zweidimensional ist, entspricht es eher einer Tabelle. Ein einfaches "Array" mit Werten ist aber relativ unspektakulär. Interessant wird ein Array, wenn jede Zeile selbst ein PSObject darstellt und man diese einfach aneinander ketten kann:

[array]$result=@()
$result+=($entry=""| select Vorname,Nachname); $entry.Vorname="Frank"; $entry.Nachname="Carius"; $entry
$result+=($entry=""| select Vorname,Nachname); $entry.Vorname="Uwe"; $entry.Nachname="Ulbrich"; $entry
$result


Vorname   Nachname
-------   --------
Frank     Carius
Uwe       Ulbrich

So schnell kann man dann eine Liste aufbauen, die mit Export-CSV etc. Natürlich auch sehr einfach ausgegeben und weiter verarbeitet werden kann. Einträge können hier im Gegensatz zur Hashtable auch mehrfach erscheinen.

Allerdings ist ein Array in der Regel "Statisch", wie man mit einer Abfrage auch sehen kann

$result.isfixedsize
true

Ein Zugriff mit der ADD oder REMOVE-Methode funktioniert nicht. Irritierend kann aber sein, dass ein Erweitern mit "+=" dennoch möglich ist. So ganz konsistent ist das nicht.

Beim Export-CSV werden die Feldnamen des ersten Eintrags als Tabellenüberschrift genutzt.

Arrayliste

Eine Erweiterung von Arrays ist eine Arrayliste. Manchmal stört bei einem Arrays, dass es eine statisch Größe hat und nicht einfach erweitert werden kann. Dann kann so eine Liste eine Lösung sein. Hier kann dann auch mit ADD und REMOVE gearbeitet werden.

PS C:\> [System.Collections.ArrayList]$ArrayList=@()
PS C:\> $ArrayList.GetType()

IsPublic IsSerial Name      BaseType
-------- -------- ----      --------
True     True     ArrayList System.Object

PS C:\> $ArrayList.add("Frank")
0
PS C:\> $ArrayList.add("Carsten")
1
PS C:\> $ArrayList
Frank
Carsten
PS C:\> $ArrayList.remove("Otto")

Beim "Add" wird als Rückgabe die aktuell Größe ausgegeben. Ein Remove eines nicht vorhandenen Elements liefert keine Fehlermeldung. Es ist auch nicht vorgeschrieben, dass die Elemente der Liste vom gleichen Typ sind. Das kann schon "verwirren", wie das folgende Beispiel zeigt, indem ich ein "Array" als drittes Element addiere

PS C:\> $ArrayList.add((1,2))
2
PS C:\> $ArrayList
Frank
Carsten
1
2
PS C:\> $ArrayList.Count
3

Bei der Ausgabe sieht es aus, also ob hier vier Elemente drin sind. Real sind es aber nur drei, bei der Powershell das dritte Element entsprechend ausgibt. Wer aber Element für Element durchläuft, bekommt die Information schon einzeln

PS C:\> $ArrayList[2]
1
2

PSObject / PSCustomObjekt

Eine ebenfalls nützliche Art Werte zu speichern ist PSObject. Man kann hier mehrere Werte als "Gruppe" speichern. Besonders gut z.B. Zusammenfassungen während der Laufzeit zu erfassen und am Ende auszugeben.

$newpso = New-Object PSObject -Property @{
         mailaddress = "NotSet" primärySmtpAddress = "NotSet"
         totalItem = 0
         date1 = (Get-Date 01.01.1900)
         date2 = (Get-Date 01.01.1900)
         date3 = (Get-Date 01.01.1900)
      }

Weitere Links