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.