ZipFiles mit der PowerShell bestücken


Thomas Wölfer
Thomas Wölfer

19. Oktober 2007


Ich hatte vor kurzem ein kurzes Fragment eines PowerShell Skripts gepostet, mit dem man "ganz einfach" eine ZIP-Datei anlegen kann. Damit will man natürlich auch was tun - in meinem Fall will ich da Dateien reintun, weil die dann später in gezippter Form per eMail versendet werden sollen. Dazu braucht man zunächst ein Folder-Objekt. Das bekommt man relativ einfach per new-Object:

function CreateZip( $path )
{
   set-content $path ("PK" + [char]5 + [char]6 + ("$([char]0)" * 18))
   $zipFile = (new-object -com shell.application).NameSpace( $path )
   return $zipFile
}

Das Folder Objekt (das ist, was man tatsächlich bekommt) hat dazu eine passende Methode: CopyHere(). Wenn man danach sucht, dann findet man auch schnell Beispiele, wie man damit Dateien in die Zip-Datei bekommt. Angenommen, man hat eine Liste von vollständigen Pfaden zu Dateien in $files, dann geht das laut den Beispielen so:

$files | foreach {
   $zipFile.CopyHere( $_.fullname )
}
 

Das Problem dabei: Das klappt nicht richtig. Manchmal sind dann nämlich alle Dateien drin, manchmal nur die erste oder die letzte, und manchmal bekommt man eine Fehlermeldung, das die Zip-Datei defekt sei.

Ich habe ein bisschen nachgewühlt und die Symptome untersucht, und meiner Ansicht nach ist der Grund dafür der, das (im Gegensatz zum normalen Verhalten von Methoden in COM-Objekten) die Methode CopyHere() asynchron ist. Das bedeutet, der Aufruf von CopyHere() kommt sofort zurück, startet aber vorher einen asynchronen Vorgang der das eigentliche kopieren und komprimieren besorgt.

Macht man das also in einer Schleife für mehrere Dateien, laufen mehrere dieser asynchronen Vorgänge gleichzeitig - und kommen sich natürlich beim schreiben in die Zip-Datei in die Quere, da es offenbar keine Instanz gibt, die sich dann wieder um synchronisieren kümmert. Man muss das also selbst machen - zumindest habe ich keinen anderen Weg gefunden.

Man kann es aber dankbarerweise selbst machen, indem man überprüft, was das Zipfile über seinen eigenen Inhalt denkt: Das Folder-Objekt das das Zipfile abbildet, hat nämlich eine Methode Items(), und die hat ein Property Count. Man kann also nachsehen, ob bereits die Anzahl an Files im Zip sind, von denen man denkt das sie drin sein sollten. Sind sie es nicht, dann muss man noch ein bisschen warten. Eine funktionierende Method sieht also so aus:

$filesCopied = 1
$files | foreach { 
   $zipFile.CopyHere( $_.fullname )
   do {
      start-sleep -milliseconds 500
   } 
   while ( $zipFile.Items().count -lt $filesCopied )
   $filesCopied++;
}