Archiv für den Monat: September 2016

Tipp: Verzeichnisinhalte in eine Zip-Datei packen mit PowerShell 3.0 oder 4.0 und .NET 4.5

Das Packen von Dateien in eine Zip-Datei ist ein Dauerbrenner. Auch in meinen PowerShell-Schulungen. Auch wenn es mit der PowerShell 5.0 endlich zwei eingebaute Cmdlets Compress-Archive und Expand-Archive im Modul Microsoft.PowerShell.Archive gibt. Es gibt unzählige Lösungen und Lösungsvorschläge im Internet, die alle mehr oder weniger zuverlässig funktionieren. Die folgende Variante setzt auf die Klasse ZipFile im Namespace System.IO.Compression, die aber erst mit .NET 4.5 eingeführt wurde, so dass sie unter einem Windows 7.0 mit PowerShell 2.0 und .NET 4.0 nicht funktioniert.

Tipp: Wer sich für den Quellcode des Archive-Moduls intessiert, dieser steht ebenfalls unter GitHub zur Verfügung: https://github.com/PowerShell/Microsoft.PowerShell.Archive.

Hier ein Beispielaufruf mit einem fiktiven Verzeichnispfad:

Ab der PowerShell 5.0 wird dank dem Compress-Archive-Cmdlet alles ein wenig einfacher:

Tipps für Zwischendurch: Die Werte einer Liste kopieren anstatt einer Referenz auf die Liste

Das folgende Thema ist etwas spezieller, aber nicht unwichtig, da die Situation in Skripten, die irgendwelche Daten in Arrays oder Listen ablegen, auftreten wird.

Eine Liste ist eine spezielle Form von Array. Sie besitzt keine feste Größe und Methoden wie Add und Remove. Eine generische Liste ist eine Liste, die nur Elemente eines vorher festgelegten Typs enthalten kann.

Der folgende Befehl definiert eine generische Liste von Int-Werten:

Der folgende Befehl fügt ein paar Zahlen in die Liste:

Einfacher ginge es per +=, doch dann funktioniert aus mir im Moment nicht bekannten Gründen die Remove-Methode nicht mehr – wir erhalten den Fehler „Die Liste hatte eine feste Größe.“

Der folgende Befehl kopiert die Variable $Zahlen1 nach $Zahlen2

Wird jetzt per Remove-Methode ein Wert aus $Liste2 entfernt, wirkt sich das Entfernen auch auf den Inhalt von $Liste1 aus:

Der Grund ist, dass durch das Zuweisen von $Zahlen1 an $Zahlen2 nur eine Referenz kopiert, aber keine neue Liste angelegt wird.

Um diesen Effekt zu vermeiden, habe ich nur eine Lösung gefunden. Sie besteht darin, alle Werte der Liste über die Erweiterungsmethode ForEach zu kopieren:

Die folgende, eigentlich gleichwertige Variante, funktioniert nicht:

In diesem Fall führt der Aufruf von Remove() zu dem besagten Fehler.

Tief verborgen im Quellcode der PowerShell wird sich die Lösung für dieses Verhalten sicher finden lassen. Oder ich übersehe wieder einmal eine offensichtliche Kleinigkeit.

Das lästige „Hop-Problem“ bei PowerShell-Remoting und eine einfache Lösung

Soll ein Befehl, der per PS-Remoting ausgeführt wird, eine Aktion ausführen, die eine Berechtigung erfordert, z.B. der Zugriff auf eine Freigabe, ist eine erneute Authentifizierung erforderlich, da die beim Aufruf von Invoke-Command übergebenen Credentials nicht weitergereicht werden. Leider gibt es keine optimale Lösung für einen solchen „Double Hop“, auch wenn PowerShell-Experte Ashley Mc Glone das Problem in seinem Blog-Eintrag für gelöst erklärt hat:

PowerShell Remoting Kerberos Double Hop Solved Securely

Das Problem ist, dass seine Lösung Windows Server 2012 voraussetzt was auch im Jahr 2016 bei großen Unternehmen und Organisationen noch nicht der Standard ist. Gesucht ist daher eine Lösung, die auch mit Windows Server 2008 R2, Windows 7 und vor allem einer älteren PowerShell-Version funktioniert.

Geht es nur um den Zugriff auf eine Freigabe ist meine Empfehlung, das erforderliche Kennwort als PSCredential-Objekt zu übergeben und die Freigabe mit Hilfe des WshNetwork-Objekts, das Teil des Windows Scripting Host (WSH) und ein fester Bestandteil von Windows ist, anzusprechen und es nicht per Net Use zu probieren.

Das folgende Beispiel ist etwas umfangreicher (ich werde es in naher Zuukunft etwas ausführlicher kommentieren). Es setzt PowerShell 4.0 voraus, da ich die statische New-Methode für das Anlegen des PSCredential-Objekts anstelle von New-Object verwende. Auf dem Remote-Computer genügt dagegen PowerShell Version 2.0. Sein Name, im Beispiel heißt er Starbase1 muss genauso geändert werden wie der Name der Freigabe und der CSV-Datei, die per Import-CSV eingelesen wird, um die Versionsnummern von Programmdateien auf dem Remote-Computer zu überprüfen.

Was gibt ein Cmdlet denn genau zurück?

In meinen PowerShell-Schulungen gebe ich mich mir jedes Mal sehr viel Mühe die Rolle der Objekte zu erklären. Alle Cmdlets, die etwas zurückgeben, geben Objekte zurück. Ein Objekt fasst die Daten über einen Gegenstand wie einen Prozess, ein Benutzerkonto oder ein Laufwerk so zusammen, dass diese Daten über Namen, die Eigenschaften genannt werden, einfach abgerufen werden können usw. usw.

Dabei erwähne ich auch, dass es unterschiedliche Sorten von Objekten gibt und man per Get-Member-Cmdlet herausbekommt, welche Sorte von Objekte ein Objekt zurückgegeben hat.

Doch das ist natürlich nur die vereinfachte Version. Vor kurzem kam die Frage auf, dass es ja Cmdlets gibt, die unterschiedliche Sorten von Objekten zurückgeben (stimmt) und ob man nicht vorab abfragen kann, welche Sorten ein Cmdlet insgesamt zurückgegeben kann.

Müsste sicher irgendwie gehen. Eigentlich weiß man es ja schon im voraus, wie bei Get-Process, und bei Cmdlets wie Get-Item hängt der Tyop vom Laufwerk ab und da es theoretisch unendlich viele Laufwerkstypen geben kann, kann es entsprechend auch unendlich viele Objekttypen geben. Bei bei Cmdlets wie Get-Command hängt der Typ des Rückgabeobjekts natürlich davon ab, welche Wert bzw. welche Werte dem CommandType-Parameter übergeben werden.

Doch da gibt es doch noch diese unscheinbare Information in der Hilfe zu einem Cmdlet, die den Typ des oder der Rückgabeobjekte angibt oder ein „none“, wenn ein Cmdlet keine Rückgabe besitzt. Zwar hängt es von der Gewissenhaftigkeit des Autors des Cmdlets ab, ob er oder sie hier etwas einträgt, doch bei bei den meisten Cmdlets ist dies der Fall.

Die folgende kleine Function gibt zu einem Cmdlet den oder die Typen der Objekte aus, die von dem Cmdlet zurückgegeben werden.

Aufgerufen wird die Function wie folgt: