Jak utworzyć archiwum ZIP?

przez | 2024-11-09

Czasami chcemy utworzyć archiwum ZIP. To rozwiązanie przydaje się w kilku sytuacjach:

  • Piszemy makro wykonujące kopię bezpieczeństwa i „dla estetyki” bądź wygody w zarządzaniu kopiami bezpieczeństwa chcemy, aby było jedno wygodne archiwum a nie np. kilkanaście luźnych plików.
  • Plik ZIP zajmuje (zazwyczaj) mniej miejsca niż plik oryginalny (aczkolwiek nie dotyczy to plików, które już same w sobie są skompresowane np. docx, xlsx, avi, zip, rar, jpeg itp.)
  • Wiele różnych plików to tak naprawdę archiwa zip (np. xlsx). Chcąc zmodyfikować taki plik xlsx (np. zdjęcie ochrony arkusza) należy go najpierw rozpakować, dokonać zmiany w którymś z rozpakowanych plików xml i ponownie go skompresować.

No OK, a jak najprościej utworzyć archiwum zip? Można skorzystać z jakiegoś zewnętrznego programu np. 7z.exe będący częścią pakietu 7-zip i go uruchamiać. Ale to rozwiązanie ma kilka wad:

  • Należy z własnym makrem rozpowszechniać ten plik
  • Trzeba jakoś wykryć, że np. utworzenie archiwum się nie powiodło co nie zawsze jest łatwe
  • Kwestie licencyjne: należy się upewnić, czy dany program (np. owy 7z.exe) można rozpowszechniać z własnym oprogramowaniem (za które możemy pobierać opłatę).

Dlatego wybrałem inny sposób. Można skorzystać z windowsowego mechanizmu tworzenia plików zip. Rozwiązanie jest proste, ale takie średniawe (w porównaniu do normalnych archiwizerów ten windowosowy mechanizm często łapie zadyszkę – ale jeśli nie kompresujesz wielogigabajtowych plików, to daje radę).

Tutaj skorzystamy z obiektu Shell.Application. Paradoksalnie nie ma w nim metody na utworzenie nowego pliku zip. Co najwyżej możemy skopiować pliki do istniejącego już folderu skompresowanego (pliku zip). Tak więc najpierw należy utworzyć pusty plik zip. Według specyfikacji pusty plik zip to 22 bajowy plik o strukturze bajtów:

0x50, 0x4B, 0x05, 0x06 i potem jeszcze 18 bajtów 0x00

(dziesiętnie będą to bajty o kodach ASCII: 80, 75 5, 6 i dalej mamy osiemnaście znaków o kodzie ASCII 0).

Do tworzenia pustych plików ZIP mamy taką oto procedurę:

' Inspiracja:
' https://www.rondebruin.nl/win/s7/win001.htm
Sub NewZip(sPath)
    Open sPath For Output As #1
    Print #1, Chr$(80) & Chr$(75) & Chr$(5) & Chr$(6) & String(18, 0);
    Close #1
End Sub

Mając już utworzony plik ZIP możemy skopiować wszystkie pliki z wybranego folderu do naszego pliku zip:

Private Sub ZipujCalyFolder(fPath, FileZ)
    Dim FileNameZip, FolderName
    Dim oApp As Object

    FolderName = fPath
    FileNameZip = FileZ

    'Create empty Zip File
    NewZip (FileNameZip)

    Set oApp = CreateObject("Shell.Application")
    'Copy the files to the compressed folder
    oApp.Namespace(FileNameZip).CopyHere oApp.Namespace(FolderName).Items

    'Keep script waiting until Compressing is done
    On Error Resume Next
    Do Until oApp.Namespace(FileNameZip).Items.Count = _
       oApp.Namespace(FolderName).Items.Count
       Application.Wait (Now + TimeValue("0:00:01"))
    Loop
    On Error GoTo 0
End Sub

A oto prosty kod wywołujący naszą procedurę

Sub TestZip()
    ZipujCalyFolder "v:\pliki\", "v:\pliki.zip"
End Sub

Jeśli chcielibyśmy skompresować jeden plik do archiwum zip, to możemy nieco uprościć naszą procedurę:

Public Sub ZipujJedenPlik(fFile, FileZ)
    Dim FileNameZip, FileToAdd
    Dim oApp As Object

    FileToAdd = fFile
    FileNameZip = FileZ

    'Create empty Zip File
    NewZip (FileNameZip)

    Set oApp = CreateObject("Shell.Application")
    'Copy the files to the compressed folder
    oApp.Namespace(FileNameZip).CopyHere FileToAdd

    'Keep script waiting until Compressing is done
    On Error Resume Next
    Do Until oApp.Namespace(FileNameZip).Items.Count = 1
    Application.Wait (Now + TimeValue("0:00:01"))
    Loop
    On Error GoTo 0
End Sub

Zwróć uwagę na pętlę Do Until… Loop. W tej co sekundę sprawdzamy, czy liczba plików w archiwum zip jest taka sama jak liczba plików w naszym folderze z którego kopiujemy pliki. Na końcu jest wywoływana metoda Application.Wait, która wstrzymuje wykonywanie naszego makra na 1 sekundę przed wykonaniem kolejnego przebiego pętli Do Until…Loop. O metodzie Application.Wait więcej pisałem w notce: Jak wstrzymać wykonywanie kodu na kilka sekund?

Dlaczego jest taka konstrukcja? Otóż musimy poczekać aż obiekt Shell.Application faktycznie skompresuje pliki (skopiuje pliki do folderu skompresowanego) co może chwilę potrwać. Dzięki temu jeśli w dalszej części makra np. usuwasz pliki które zostają dodawane do archiwum to unikniesz problemu jakim byłoby skasowanie pliku, zanim obiekt Applcation.Shell zdążyłby go skopiować do naszego zipa!

Uwaga: tutaj taka ciekawostka: pisząc procedurę tworzącą archiwum musi ono mieć koniecznie rozszerzenie zip (nawet jeśli jawnie „edytuję” np. plik docx, xlsx czy jar). Gdy plik ma inne rozszerzenie niż zip to obiekt Shell.Application „głupieje”. Po utworzeniu archiwum zawsze za pomocą instrukcji Name można plikowi zip zmienić nazwę (rozszerzenie) na inną.

Uwaga2: mechanizm tworzenia plików ZIP jest nieco upośledzony. Otóż w nazwach plików, które chcemy dodać do archiwum nie mogą się znajdować znaki o następujących kodach ASCII (archiwum nie zostanie utworzone; inne archiwizatory np. WinRAR nie mają z tym problemu): 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 139, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 155, 166, 169, 174, 177, 181, 183. Nie jestem specjalistą który jakoś wnikliwie zna strukturę formatu ZIP ale prawdopodobnie są to znaki, których odpowiedników nie ma w stronie kodowej CP437 (domyślna DOSowa strona kodowa dla rynku amerykańskiego) a są w stronie kodowej Windows-1250. O dziwo polskie ogonki (których na próżno szukać w stronie kodowej CP437) są odpowiednio „przemapowane” na inne znaki.

Wskazówka: zobacz też bliźniaczy wpis: jak rozpakować plik ZIP.

Dodaj komentarz

Twój adres e-mail nie zostanie opublikowany. Wymagane pola są oznaczone *

Witryna wykorzystuje Akismet, aby ograniczyć spam. Dowiedz się więcej jak przetwarzane są dane komentarzy.