Die Ausführung Ihrer PowerShell-Skripte dauert zu lange und führt immer zu Zeitüberschreitungen? Der Arbeitsspeicher muss wieder freigegeben werden. Wie es funktioniert, erfahren Sie hier. Die PowerShell wird vielfach zur Automatisierung von Aufgaben oder zur Konfiguration von Systemen, wie z.B. Exchange verwendet. Die Verwendung der PowerShell ist teilweise jedoch sehr ressourcen- bzw. speicherintensiv, z.B. bei Dateioperationen. Auch bei der Ausführung von Exchange Cmdlets für die Verwaltung von Exchange OnPrem und Exchange Online/M365 kann es dazu kommen, dass durch den PowerShell Prozess viel Arbeitsspeicher belegt wird. Der belegte Speicher kann dabei im Gigabyte Bereich liegen.
Typische Fehlerbehandlung und ihre Tücken
Wird der PowerShell Prozess z.B. durch Schließen der PowerShell Shell (PowerShell Konsole) wieder beendet, wird in der Regel der belegte Speicher schnell wieder freigegeben. Was ist aber, wenn wir im selben PowerShell Prozess weitere PowerShell-Befehle oder teilweise komplexe Skripte ausführen, die verschiedene Cmdlets aufrufen. Dann nimmt der Speicherverbrauch immer weiter zu, was dazu führt, dass das System immer langsamer wird. Dieses Verhalten betrifft im Übrigen nicht nur Windows Systeme, sondern auch Linux und macOS. Linux und macOS werden aber erst ab der PowerShell Version 6 unterstützt.
Wie können wir also dafür sorgen, dass der Speicher wieder freigegeben wird?
Dafür gibt es nach meinem Wissensstand 3 Möglichkeiten, die auch miteinander kombiniert werden können.
Index
Verwendung des Garbage collectors
Die PowerShell basiert auf der .NET Common Language Runtime (CLR). Dadurch stehen je nach Betriebssystem alle bzw. fast alle Möglichkeiten der .NET Runtime auch unter PowerShell zur Verfügung.
Einer der Vorteile der .NET Runtime ist, dass in dieser der nicht mehr benötigte Arbeitsspeicher automatisch wieder freigegeben wird und dadurch PowerShell Zeitüberschreitung vermeiden kann. Dies erfolgt durch den Garbage Collector (GC).
Jedoch haben wir keinen direkten Einfluss auf die automatische Bereinigung, d.h. diese kann sofort oder zu einem späten Zeitpunkt erfolgen.
Möchten wir die Bereinigung der Arbeitsspeicher in seinen Skripten sofort anfordern, rufen wir den Befehl „[System.GC]::Collect();“ auf.
Für weitergehende Informationen zum Garbage Collector ist die Dokumentation von Microsoft hilfreich: https://learn.microsoft.com/en-us/dotnet/standard/garbage-collection/
Umleitung von Ausgaben auf $null
Viele PowerShell Cmdlets liefern eine Ausgabe zurück, die an den PowerShell-Host, in der Regel die PowerShell Konsole, weitergegeben wird. Auch das führt dazu, dass der Arbeitsspeicher weiter belegt wird. Wird die Ausgabe nicht benötigt, können wir diese an $null umleiten.
Hierfür gibt es 3 Optionen:
Verwendung des Cmdlets Out-Null
Das Cmdlet Out-Null leitet eine Ausgabe an NULL weiter. Dadurch wird aus der Pipeline gelöscht und auch nicht angezeigt.
1 |
Connect-ExchangeOnline | Out-Null |
Weitere Informationen finden Sie wieder bei Microsoft: https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/out-null?view=powershell-7.4
Umleitung zu $null
Um eine Ausgabe an NULL umzuleiten, kann auch der Umleitungsoperator „>“ verwendet werden.
1 |
Connect-ExchangeOnline > $null |
Zuweisung zu $null
Eine dritte Option ist, dass wir die Ergebnisse eines Befehls $null zuweisen.
1 |
$null = Connect-ExchangeOnline |
Löschen von nicht mehr benötigten Variablen
Häufig verwenden wir in PowerShell-Skripten, wie in anderen Programmiersprachen auch, Variablen. Auch für Variablen und deren Inhalt wird Speicher reserviert. Die nicht mehr benötigte Variablen zu löschen, hilft auch PowerShell zu beschleunigen und Zeitüberschreitung zu vermeiden.
Hierfür können wir die Cmdlets „Remove-Variable“ oder „Clear-Variable“ verwenden, oder wir weisen der Variable einfach den Wert $null zu.
Dabei gibt es aber auch eine Besonderheit, da wir wissen müssen.
Als Beispiel soll folgender Sachverhalt dienen:
Es wird eine Variable $text mit dem Inhalt „Hallo Welt“ definiert.
1 |
$text = „Hallo Welt“ |
Setzen der Variable auf $null
Eine Variable können wir auf null setzen, in dem wir schreiben:
1 |
$text = $null |
Hier ist die Besonderheit, dass wir die Variable nicht löschen. Wir löschen nur den Inhalt der Variablen. Die Variable selbst und damit ihre Speicherreservierung bleibt weiterhin bestehen.
Das können wir überprüfen, in dem wir den Befehl Get-Variable text eingeben. Dabei wird der Variablen-Name ohne das führende $ geschrieben.
Führen wir diesen Befehl aus, erhalten wir:
Verwendung des Cmdlet Clear-Variable
Mit dem Cmdlet Clear-Variable können wir ebenfalls den Wert der Variablen löschen. Aber auch hier bleibt die Variable selbst erhalten.
1 |
Clear-Variable text |
Der Variablen-Name wird auch hier ohne das führende $ geschrieben.
Verwendung des Cmdlet Remove-Variable
Der bevorzugte Weg zum Löschen einer Variablen und damit dem wieder Freigeben deren Arbeitsspeicher, ist die Verwendung von Remove-Variable.
1 |
Remove-Variable text |
Der Variablen-Name wird ebenfalls ohne das führende $ geschrieben.
Rufen wir jetzt den Befehl Get-Variable text auf, wird eine Exception ausgelöst, da es die Variable nicht mehr im System gibt.
PowerShell Zeitüberschreitung vermeiden beim Zugriff auf Exchange
Um Exchange OnPrem oder Exchange Online zu verwalten, muss ebenfalls die PowerShell verwendet werden, da über die Administrations-Oberflächen nur ein Teil verwaltet werden kann.
Exchange Online
Um sich mit Exchange Online zu verbinden, muss als Erstes eine Verbindung per Cmdlet Connect-ExchangeOnline hergestellt werden.
1 |
$null = Connect-ExchangeOnline |
(https://learn.microsoft.com/en-us/powershell/module/exchange/connect-exchangeonline?view=exchange-ps)
Dieses Cmdlet gibt ebenfalls eine Ausgabe zurück, die wir $null zuweisen können, wenn diese nicht benötigt wird. Zusätzlich legt dieses Cmdlet aber auch tmpEXO Dateien im Temp-Verzeichnis des Computers (%localappdata%\temp) an, auf dem das Cmdlet aufgerufen wurde.
Nutzen wir in einem Skript häufig Connect-ExchangeOnline, werden immer neue tmpEXO Dateien angelegt. Um das zu verhindern, sollten wir immer
1 |
$null = Disconnect-ExchangeOnline -Confirm:$false |
verwenden. Mit diesem Cmdlet beenden wir die Exchange Session wieder.
(https://learn.microsoft.com/en-us/powershell/module/exchange/disconnect-exchangeonline?view=exchange-ps)
Wie wir sehen, wird die Ausgabe des Cmdlet wieder $null zugewiesen. Über den Parameter „-Confirm“ wird nur festgelegt, ob eine Bestätigungsaufforderung angezeigt werden soll oder nicht. Im obigen Beispiel wird keine Bestätigungsaufforderung angezeigt.
Auch wenn wir es nicht möchten, können beim Ausführen von PowerShell Fehler auftreten. Treten diese zum Beispiel nach dem Aufruf von Connect-ExchangeOnline auf und es wurde keine korrekte Fehlerbehandlung implementiert, bleibt die Exchange Session geöffnet. Dadurch sind auch die temporären Dateien weiterhin vorhanden.
Deshalb ist es sinnvoll, Connect-ExchangeOnline in eine try..catch..finally Anweisung zu setzen. Der Aufruf von Disconnect-ExchangeOnline sollte dabei im finally Block stattfinden, da dieser immer, d.h. auch bei einem auftretenden Fehler, durchlaufen wird.
Im Beispiel wurde der Aufruf von Disconnect-ExchangeOnline zusätzlich nochmal in eine separate try-catch-Anweisung gesetzt. Kommt es beim Aufruf von Disconnect-ExchangeOnline zu einem Fehler, können wir diesen protokollieren und einen unkontrollierten Abbruch des Codes oder des Skriptes verhindern.
Exchange OnPrem
Um sich Remote mit Exchange OnPrem zu verbinden, wird eine Remote PowerShell-Session verwendet. Dazu werden nacheinander die 3 Cmdlets aufgerufen:
1 2 3 4 5 6 7 |
$credential = Get-Credential $psSession = New-PSSession -ConfigurationName Microsoft.Exchange -ConnectionUri http://<ServerFQDN>/PowerShell/ -Authentication Kerberos -Credential $credential Import-PSSession $psSession -DisableNameChecking |
Danach kann die Exchange Cmdlets zur Steuerung von Exchange OnPrem verwenden.
Wird der Zugriff auf Exchange OnPrem nicht mehr benötigt, sollten wir die Session und auch die Variablen vom Arbeitsspeicher wieder freigeben.
Zum Beenden der Session wird das Cmdlet Remove-PSSession aufgerufen:
1 |
Remove-PSSession -Id $psSession |
Dieses Cmdlet schließt die aktuelle PowerShell Session, stoppt alle Befehle, die in dieser Session ausgeführt werden und gibt die verwendeten Ressourcen wieder frei. Auch die verwendeten Variablen sollten per Remove-Variable wieder gelöscht werden.
Für die Verwendung der Exchange Online und Exchange OnPrem Cmdlets gilt ebenfalls, dass wir alle Cmdlets, die eine Ausgabe zurückliefern und die wir nicht benötigen, am besten auf $null umleiten, um Ressourcen zu sparen.
Fazit
Um PowerShell Zeitüberschreitung zu vermeiden, müssen Ressourcen gespart werden. Es gibt dafür verschiedene Möglichkeiten. Denn auch wenn Arbeitsspeicher und Festplattenspeicher heute relativ preiswert sind, sollten wir trotzdem versuchen, mit diesen sorgsam umzugehen.
Die mir bekannten und auch von mir selbst verwendeten Möglichkeiten der Ressourceneinsparung habe ich in diesem Artikel genannt. Diese können einzeln oder auch in Kombination verwendet werden und sind relativ unkompliziert anzuwenden.
Unterstützung benötigt?
Gerne stellen wir Ihnen unsere Leistungen und Lösungen in einem persönlichen Gespräch vor und würden uns über Ihre Kontaktaufnahme sehr freuen!
Leave a Reply
<p>Danke für Ihre Anregungen, Fragen und Hinweise.<br/>Infos zum <a href="https://www.active-directory-faq.dekontakt/">Datenschutz</a></p>