Die Webseite dotnet-snippets.de wird 5 Jahre alt. Herzlichen Glückwunsch!
Wer die Seite noch nicht kennt, aber mit Microsoft .NET entwickelt, sollte einfach mal vorbeischauen. Es gibt viele kurze hilfreiche Codeschnipsel zu den meisten Problemchen, die man so im täglichen Leben hat.
Für eine Funktionalität auf einem Buildserver wurde ein Skript benötigt, dass in der Lage ist, Archive zu erstellen. Ein idealer Anwendungsfall für Powershell, wenn man erst einmal ein paar wichtige Sachen erkannt hat.
Das Skript soll zwei Parameter empfangen. Der erste Parameter $destname enthält lediglich den Zielnamen des Archiv’s. Der zweite Parameter $source ist der Ordner, wo alle zu packenden Dateien abgelegt sind. {dest} und {temp} sind Platzhalter fester Pfade für das zu erstellende Archiv und temporäre Dateien.
param ([string] $destname, [string] $source)
$path = "{dest}\" + $destname + ".zip"
$temp = "{temp}" |
Um das Skript einfach einsetzen zu können, sollte in Powershell kein Modul mehr nachinstalliert werden müssen. Darum wurde auf die Funktionalität der gepackten Ordner von Windows zurückgegriffen. Diese gibt es schon geraume Zeit im Standard-Lieferumfang. Um diesen Ordner nutzen zu können, erstellt man ein leeres Archiv mittels einer kleinen Bytefolge, dass dann einfach wie ein Ordner angesprochen wird (mittels des COM-Objektes Shell.Application):
Set-Content $path ("PK" + [char]5 + [char]6 + ("$([char]0)" * 18))
$ShellApp = New-Object -ComObject Shell.Application
$ZipPackage = $ShellApp.Namespace($path) |
Dieser Zwischenschritt löscht dem temporären Ordner und kopiert eine Auswahl von Dateien aus dem Quellverzeichnis in den frisch erstellen temporären Ordner:
rmdir $temp -Recurse -Force
mkdir $temp
$files = Get-ChildItem -Path $($source + "\*") -Include *.exe,*.dll | Copy-Item -Destination $temp
mkdir $($temp + "\Data")
$files = Get-ChildItem -Path $($source + "\Data\*") -Include *.* | Copy-Item -Destination $($temp + "\Data") |
Der letze Schritt war der schwerste: Als erstes wird der komplette temporäre Ordner einfach in das Archiv kopiert. Leider ist dieser Vorgang asynchron. Beendet man jetzt das Skript und die Powershell, so wird der Vorgang auch mit abgebrochen. Also muss man mittels Sleep solange warten, bis das Archiv garantiert erstellt ist. Diese Krücke ist zwar unbefriedigend, aber leider ist mir dafür keine alternative Lösung eingefallen:
$ZipPackage.CopyHere($temp, 4)
Start-Sleep -s 15 |
In einem Backup-Ordner stand die Aufgabe an, alte Dateien automatisch löschen zu lassen. Dazu wurde folgendes Skript eingesetzt:
Get-ChildItem -Path . |
Where-Object -FilterScript {($_.LastWriteTime -lt [DateTime]::Now.AddDays(-7))} |
Remove-Item -force |
Zuerst werden alle relevanten Dateien ermittelt. Danach wird auf diese Liste ein Filter gesetzt, der nur Dateien ermittelt, die älter als 7 Tage sind. Hierzu wird eine Rückgriff auf DateTime aus dem Net-Framework genutzt. Es bietet mit Now und AddDays die gewünschte Funktionalität, von jetzt 7 Tage nach hinten zu gehen. Als letzten Schritt muss die ermittelte Liste lediglich an Remove-Item weitergereicht werden.
Beim Ausführen einer Powershell-Datei erhielt ich im Protokoll neulich folgende bekannte Fehlermeldung, obwohl Set-ExecutionPolicy auf RemoteSigned gesetzt war:
Die Datei "...test.ps1" kann nicht geladen werden, da die Ausführung
von Skripts auf diesem System deaktiviert ist. Weitere
Informationen erhalten Sie mit "get-help about_signing".
Bei Zeile:1 Zeichen:2
+ . <<<< '...test.ps1'
+ CategoryInfo : NotSpecified: (:) [], PSSecurityException
+ FullyQualifiedErrorId : RuntimeException
Die Lösung lag letztendlich darin, dass das entsprechende Skript auf einem 64Bit-System als 32Bit-Task aufgerufen wurde. Es war ausreichend, Set-ExecutionPolicy unter Powershell (x86) ebenfalls aufzurufen und entsprechend zu setzen.