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. Vorsicht auch beim verschachteln mit der Pipeline Get-Recipient | ForEach-Object { foreach ($proxyaddress in ($_.EMailAddresses | Where-Object {$_.PrefixString -eq "SMTP"})) { Write-Host " Processing SMTP ProxyAddress $($proxyaddress.SmtpAddress.tolower())" } } Das "ForeEach-Object erstellt eine $_-Variable, die aber in der Schleife mit dem "Where-Object" kaputt gemacht wird. |
$$ |
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.
Variablen definieren
Wussten Sie schon, dass Sie Variablen auch "definieren" können?. Ich meine damit nicht, dass Sie vor der Verwendung einer Variable diese erst mal typisiert anlegen wie z:B.
[string]$vorname=""
Sie können die Variable auch richtig "benennen" und damit ihr Skript dokumentieren. Anscheinend machen das aber sehr wenige Programmierer. Ich versuche es aber besser zu machen. Es gibt aber noch einen netten Weg, wie Sie Variablen zuweisen und zugleich ausgeben können.
# Zuweisung ohne Ausgabe [string]$name="Frank Carius" # Zuweisung mit Ausgabe ([string]$name="Frank Carius") # Das funktioniert auch Write-host "Der Name ist:"([string]$name="Frank Carius") # Das weist zwar zu aber gibt nichts aus! Write-host "Der Name ist:$([string]$name=""Frank Carius"")" Write-host "Der Name ist:$([string]$name='Frank Carius')"
Manchmal ist es vielleicht einfacher, die Zuweisung und die Ausgabe zur Lesbarkeit sauber zu trennen.
- Describe Your PowerShell Variables
https://blogs.technet.microsoft.com/heyscriptingguy/2015/07/27/describe-your-powershell-variables/
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. Gerade mit "Float" passiert das schnell. Hier ein paar Beispiele und deren vielleicht unerwarteten Ergebnisse:
# richtiges Kommazeichen aber falsch multipliziert [float]"0.99" = 0,99 [float]"0.99"*100 = 99,0000009536743 # falsche Kommazeichen falsche werte aber bei der Multiplikation passiert nicht der Rundungsfehler [float]"0,99" = 99 [float]"0,99"*100 = 9900
Auch beim Konvertieren von String zu Zahlen kann einiges schief gehen. insbesondere der Unterschied zwischen String und Char möchte ich hier vorstellen
# Konvertierung einer Zeichenkette mit einer Zahl in einen Zahlentyp PS C:\> [int]"123" 123 PS C:\> [int]"1" 1 PS C:\> [int]([char]"1") 49
Das "Zeichen" 1 als "Char" wird bei der Konvertierung als ASCII-Code konvertiert und eben nicht als Zahlenwert.
- Built-In Types Table (C#
Reference)
http://msdn.microsoft.com/en-us/library/ya5y69ds.aspx
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
- Add-Type
http://technet.microsoft.com/en-us/library/hh849914.aspx - Hey, Scripting Guy! Weekend
Scripter: The Fruity Bouquet of
Windows PowerShell Enumerations
http://blogs.technet.com/b/heyscriptingguy/archive/2010/06/06/hey-scripting-guy-weekend-scripter-the-fruity-bouquet-of-windows-PowerShell-enumerations.aspx
Hashtables/Dictionary
Wenn Sie Informationen zur Bildung on Hashwerten suchen, dann ist die Seite PS Hash-Werte richtig
Ein Dictionary und eine Hashtable sind dahingehend gleich, dass beide einen "Key" und einen "Value" haben. Beim Dictionary typisieren Sie aber sowohl den Key als auch den Value und müssen sich dann auch daran halten. Das kann gut sein, wenn Sie damit Fehler im Code abfangen wollen und schneller ist es auch. Eine Hashtable hingegen kann unterschiedliche Inhaltstypen abspeichern.
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 -NoTypeInformation .\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.
- Convert PowerShell HashTable
to CSV File
https://www.evanreichard.com/2013/09/02/convert-powershell-hashtable-to-csv-file/ - ConvertFrom-StringData
https://technet.microsoft.com/en-us/library/hh849900.aspx - Dealing with PowerShell Hash
Table Quirks
http://blogs.technet.com/b/heyscriptingguy/archive/2011/10/16/dealing-with-powershell-hash-table-quirks.aspx - Microcode: PowerShell
Scripting Tricks: The Joy of using Hashtables with Windows
PowerShell
http://blogs.msdn.com/b/mediaandmicrocode/archive/2008/11/27/microcode-PowerShell-scripting-tricks-the-joy-of-using-hashtables-with-windows-PowerShell.aspx - PowerShell updating hash
table values in a foreach loop?
http://stackoverflow.com/questions/5879871/PowerShell-updating-hash-table-values-in-a-foreach-loop - HashTable-Class
http://msdn.microsoft.com/en-us/library/system.collections.hashtable%28VS.80%29.aspx - 11 Ways to Create HashTable
in PowerShell
https://ridicurious.com/2019/10/04/11-ways-to-create-hashtable-in-powershell/
Arrays und ArrayListe
Die früher hier vorhandenen Informationen sind auf die Seite PS und Arrays
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" primarySmtpAddress = "NotSet" totalItem = 0 date1 = (Get-Date 01.01.1900) date2 = (Get-Date 01.01.1900) date3 = (Get-Date 01.01.1900) }
Kleiner Tipp. Wenn Sie die Felder in der gleichen Reihenfolge für eine spätere Ausgabe z.B: zu Export-CSV nutzen wollen, dann können Sie auch folgende Schreibweise nutzen
$result = [pscustomobject][ordered]@{ date = get-date -Format yyyy-MM-dd time = get-date -Format hh:mm:ss remoteip = [string]$($remoteip) max = [long]0 min = [long]9999999 avg = [long]0 }
- Powershell: Everything you wanted to
know about PSCustomObject
https://kevinmarquette.GitHub.io/2016-10-28-powershell-everything-you-wanted-to-know-about-pscustomobject/ - Building Custom Object Types with
PowerShell and PSTypeName
http://www.adamtheautomator.com/building-custom-object-types-powershell-pstypename/ - Custom objects and PSTypeName –
PowerShell Station Custom objects and
PSTypeName
https://powershellstation.com/2016/05/22/custom-objects-and-pstypename/ - Update-TypeData
https://technet.microsoft.com/en-us/library/hh849908.aspx
Weitere Links
- PS Performance
-
PS JSON
Wie kann ich mit PowerShell und JSON-Daten arbeiten? - PowerShell Tutorial 7:
Accumulate, Recall, and Modify
data
http://www.PowerShellpro.com/PowerShell-tutorial-introduction/variables-arrays-hashes/