Search the Community
Showing results for tags 'zipfldr'.
-
Since XP Windows users have ability to view, compress, extract zipped files natively. Microsoft's developers decided to create shell extension that does this job. Shell extension is library that shell (explorer.exe) uses to communicate with in order to achieve results. In case of ZIP files this particular library is zipfldr.dll. How does it work? By default when you double-click ZIP file, explorer opens it allowing you to browse around it pretty much the same way as you would through the ordinary folder. To have that ability Microsoft developed COM based technology (methodology) known as Shell Extensibility. Every shell extension has to follow strict and documented set of rules which shell then uses to populate graphical interface user uses. I won't be going into details about registering shell extension, only about how they are meant to be implemented. The main interface every shell extension should implement is IShellFolder. That's the basic interface that would give all the information about the extension and it would be used to create instances of every other wanted or needed interface. Obvious question could be how to create object of IShellFolder interface for the extension. In case shell extension is implemented in form of DLL library then that particular dll must export function called DllGetClassObject. This function is used to create object of IClassFactory interface which is then used to get mentioned IShellFolder. In AutoIt it's very simple to do this first step. Ordinary DllCall in combination with ObjCreateInterface and voila, first helper function is done. I will call it __UnZIP_DllGetClassObject: Func __UnZIP_DllGetClassObject($sDll, $sCLSID, $sIID, $tagInterface = "") Local $tCLSID = __UnZIP_GUIDFromString($sCLSID) Local $tIID = __UnZIP_GUIDFromString($sIID) Local $aCall = DllCall($sDll, "long", "DllGetClassObject", "struct*", $tCLSID, "struct*", $tIID, "ptr*", 0) If @error Or $aCall[0] Then Return SetError(1, 0, 0) Local $oObj = ObjCreateInterface($aCall[3], $sIID, $tagInterface) If @error Then Return SetError(2, 0, 0) Return $oObj EndFunc Func __UnZIP_GUIDFromString($sGUID) Local $tGUID = DllStructCreate("byte[16]") DllCall("ole32.dll", "long", "CLSIDFromString", "wstr", $sGUID, "struct*", $tGUID) If @error Then Return SetError(1, 0, 0) Return $tGUID EndFunc To call the function I have to DllOpen zipfldr.dll, define CLSID_CZipFolder and of course, IClassFactory: Global Const $hZIPFLDR_DLL = DllOpen("zipfldr.dll") Global Const $CLSID_CZipFolder = "{e88dcce0-b7b3-11d1-a9f0-00aa0060fa31}" ;=============================================================================== #interface "IClassFactory" Global Const $sIID_IClassFactory = "{00000001-0000-0000-C000-000000000046}" Global Const $tagIClassFactory = "CreateInstance hresult(ptr;clsid;ptr*);" & _ "LockServer hresult(bool);" ;=============================================================================== Local $oClassFactory = __UnZIP_DllGetClassObject($hZIPFLDR_DLL, $CLSID_CZipFolder, $sIID_IClassFactory, $tagIClassFactory) Now that I have object of IClassFactory interface, I can simply ask for IShellFolder object pointer: Local $pShellFolder $oClassFactory.CreateInstance(0, $sIID_IShellFolder, $pShellFolder) ... And after defining IShellFolder interface create the ShellFolder object: ;=============================================================================== #interface "IShellFolder" Global Const $sIID_IShellFolder = "{000214E6-0000-0000-C000-000000000046}" Global $tagIShellFolder = "ParseDisplayName hresult(hwnd;ptr;wstr;ulong*;ptr*;ulong*);" & _ "EnumObjects hresult(hwnd;dword;ptr*);" & _ "BindToObject hresult(struct*;ptr;clsid;ptr*);" & _ "BindToStorage hresult(struct*;ptr;clsid;ptr*);" & _ "CompareIDs hresult(lparam;struct*;struct*);" & _ "CreateViewObject hresult(hwnd;clsid;ptr*);" & _ "GetAttributesOf hresult(uint:struct*;ulong*);" & _ "GetUIObjectOf hresult(hwnd;uint;struct*;clsid;uint*;ptr*);" & _ "GetDisplayNameOf hresult(struct*;dword;struct*);" & _ "SetNameOf hresult(hwnd;struct*;wstr;dword;struct*);" ;=============================================================================== $oShellFolder = ObjCreateInterface($pShellFolder, $sIID_IShellFolder, $tagIShellFolder) The next step is to load the ZIP file. IShellFolder doesn't have method for this but it can be asked for either IPersistFile or IPersistFolder interface objects.Which interface can be used to load the ZIP depends on system. In my tests I have found that IPersistFile works on Windows 7 and IPersistFolder on Windows 8 and XP. I will go with IPersistFile first and use IPersistFolder as an alternative in case of failure. It's really irrerelevant as long as ZIP gets loaded. These both interfaces inherit from IPersist so to define them correctly I need to define that one first: ;=============================================================================== #interface "IPersist" Global Const $sIID_IPersist = "{0000010c-0000-0000-C000-000000000046}" Global $tagIPersist = "GetClassID hresult(ptr*);" ;=============================================================================== ;=============================================================================== #interface "IPersistFile" Global Const $sIID_IPersistFile = "{0000010b-0000-0000-C000-000000000046}" Global $tagIPersistFile = $tagIPersist & _ "IsDirty hresult();" & _ "Load hresult(wstr;dword);" & _ "Save hresult(wstr;bool);" & _ "SaveCompleted hresult(wstr);" & _ "GetCurFile hresult(ptr*);" ;=============================================================================== ;=============================================================================== #interface "IPersistFolder" Global Const $sIID_IPersistFolder = "{000214EA-0000-0000-C000-000000000046}" Global $tagIPersistFolder = $tagIPersist & _ "Initialize hresult(ptr);" ;=============================================================================== ;... code below goes to some function $oPersistF = ObjCreateInterface($pShellFolder, $sIID_IPersistFile, $tagIPersistFile) If @error Then ; Try IPersistFolder $oPersistF = ObjCreateInterface($pShellFolder, $sIID_IPersistFolder, $tagIPersistFolder) If @error Then Return SetError(3, 0, 0) Else If $oPersistF.Initialize(__UnZIP_SHParseDisplayName($sZIP)) < $S_OK Then Return SetError(4, 0, 0) EndIf Else Local Const $STGM_READ = 0x00000000 If $oPersistF.Load($sZIP, $STGM_READ) < $S_OK Then Return SetError(4, 0, 0) EndIf ;... Func __UnZIP_SHParseDisplayName($sPath) Local $aCall = DllCall("shell32.dll", "long", "SHParseDisplayName", "wstr", $sPath, "ptr", 0, "ptr*", 0, "ulong", 0, "ulong*", 0) If @error Or $aCall[0] Then Return SetError(1, 0, 0) Return $aCall[3] EndFunc Now that ZIP is loaded zipfldr will do its job of unzipping a file. If you look back to IShellFolder definition you'll see that the object of that interface exposes function/method called ParseDisplayName. When invoked the interface will internally locate wanted file and give back pointer to PIDL (a form of item identifier uniquely identifying it). This data is then passed to BindToStorage.BindToStorage is function that accepts IID of the interface you want to bind item to. In my case I will go for IStream so that I can easily get decompressed binary data out of it. Before I show how to do that a word about ParseDisplayName. It appears that "paths" inside the ZIP use either forward slash or backslash depending on system. For example if you ZIP have folder "ABC" with file "x.txt" in it, on XP it would be "ABC/x.txt" and on newer systems it'd be "ABCx.txt". So in case of failure with one I'll go with another. Local Const $SFGAO_STREAM = 0x00400000 ;... Local $pPidl = 0 Local $iAttributes = $SFGAO_STREAM ; Find the file inside the ZIP If $oShellFolder.ParseDisplayName(0, 0, $sFile, 0, $pPidl, $iAttributes) < $S_OK Then $sFile = StringReplace($sFile, "\", "/") ; XP uses forward slash apparently If $oShellFolder.ParseDisplayName(0, 0, $sFile, 0, $pPidl, $iAttributes) < $S_OK Then Return SetError(5, 0, 0) EndIf ...Back to IStream (inherits from ISequentialStream): ;=============================================================================== #interface "ISequentialStream" Global Const $sIID_ISequentialStream = "{0c733a30-2a1c-11ce-ade5-00aa0044773d}" Global Const $tagISequentialStream = "Read hresult(struct*;dword;dword*);" & _ "Write hresult(struct*;dword;dword*);" ;=============================================================================== ;=============================================================================== #interface "IStream" Global Const $sIID_IStream = "{0000000c-0000-0000-C000-000000000046}" Global Const $tagIStream = $tagISequentialStream & _ "Seek hresult(int64;dword;uint64*);" & _ "SetSize hresult(uint64);" & _ "CopyTo hresult(ptr;uint64;uint64*;uint64*);" & _ "Commit hresult(dword);" & _ "Revert hresult();" & _ "LockRegion hresult(uint64;uint64;dword);" & _ "UnlockRegion hresult(uint64;uint64;dword);" & _ "Stat hresult(struct*;dword);" & _ "Clone hresult(ptr*);" ;=============================================================================== ;... Local $pStream If $oShellFolder.BindToStorage($pPidl, 0, $sIID_IStream, $pStream) <> $S_OK Then Return SetError(6, __UnZIP_CoTaskMemFree($pPidl), 0) Local $oStream = ObjCreateInterface($pStream, $sIID_IStream, $tagIStream) And that would be it as far as unzipping goes. Getting binary data out of stream is off-topic here. The udf uses function UnZIP_SaveFileToBinary for that. If you are interested then take a look at that inside the attached file. To use functions from the UDF to for example, extract file named "test.txt" inside folder named "Test" inside the folder named "Abc" inside the ZIP file "Testing.zip", you would call it like this: $sZIP = "Testing.zip" ; full path to your zip $sFile = "Abc\Test\test.txt" ; file inside the zip $bBinary = UnZIP_SaveFileToBinaryOnce($sZIP, $sFile) ; to extract to variable ; Maybe to print to console? ConsoleWrite(BinaryToString($bBinary) & @CRLF) Or if you'd want to extract it to some location on your disk: $sZIP = "Testing.zip" ; full path to your zip $sFile = "Abc\Test\test.txt" ; file inside the zip $sDestination = @DesktopDir & "\test.txt" UnZIP_SaveFileToFileOnce($sZIP, $sFile, $sDestination) For repetitive tasks on the same ZIP file you wouldn't want to use ...Once() functions, but rather than that functions without that suffix to avoid internal parsing of the whole ZIP for every file inside: $sZIP = "Testing.zip" ; full path to your zip $sFile0 = "Abc\Test\test0.txt" ; file 0 inside the zip $sFile1 = "Abc\Test\test1.txt" ; file 1 inside the zip $sFile5 = "Abc\Test\Whatever\test5.txt" ; file 5 inside the zip Local $oShellFolder, $oPersistF $bBinary = UnZIP_SaveFileToBinary($sZIP, $sFile0, $oShellFolder, $oPersistF) ; to extract to variable ; Print content of file 0 to console ConsoleWrite(BinaryToString($bBinary) & @CRLF) $bBinary = UnZIP_SaveFileToBinary($sZIP, $sFile1, $oShellFolder, $oPersistF) ; to extract to variable ; Print content of file 1 to console ConsoleWrite(BinaryToString($bBinary) & @CRLF) $bBinary = UnZIP_SaveFileToBinary($sZIP, $sFile5, $oShellFolder, $oPersistF) ; to extract to variable ; Print content of file 5 to console ConsoleWrite(BinaryToString($bBinary) & @CRLF) ; Free objects $oShellFolder= 0 $oPersistF = 0 ...You get the idea. Technique used here is pretty much unique. I have't seen any code that uses this to-the-point method written in any language. To use it backward check this article. Oh and I took CLSID_CZipFolder name from here because they deserve it. UDF: UnZIP.au3 You can find example of usage >here.
- 1 reply
-
- zipfldr
- CLSID_CZipFolder
-
(and 1 more)
Tagged with: