Announcement

Collapse

Forum Guidelines

This forum is for finished source code that is working properly. If you have questions about this or any other source code, please post it in one of the Discussion Forums, not here.
See more
See less

Using Windows Zipping, Without Runaway Threads

Collapse
X
  • Filter
  • Time
  • Show
Clear All
new posts

  • Using Windows Zipping, Without Runaway Threads

    Sure, you can zip with the scripting interfaces but what is this, VBScript? Let's do it the big boy way.

    Not just for style points or bravado, but for two reasons:
    The Namespace, CopyHere method (ie the code in post #9 here and its variations https://forum.powerbasic.com/forum/u...697#post610697) has the drawback that the zipping is done on a seperate thread that you can't control. Unless you know exactly what you're zipping, this means you have to work out how long to sleep so you don't exit the program while its doing its stuff.

    The other reason is that if the zipping takes long enough, you will get the progress UI regardless of which flags you pass to CopyHere, since zipfldr ignores them.

    So let's pidl about and use the crusty old IStorage and IShellFolder COM interfaces meant for proper languages and get this guy under our control, albeit with a generous amount of fudging

    This was built with PBWin 10.03 (& Jose's includes) but it shoud be run as a command line app, either

    ZipAThing MyThingToZip myfile.zip
    ZipAThing myfile.zip ZipOutputFolder -u

    Code:
    #Dim All
    #Compile Exe "ZipAThing.exe"
    %UNICODE=1
    #Include once "WIN32API.INC"
    #include once "shlwapi.inc"
    #include once "shlobj.inc"
    #include once "dbghelp.inc"
    
    DECLARE FUNCTION fputws CDECL IMPORT "msvcrt.dll" ALIAS "fputws" ( _
        BYREF string AS WSTRINGZ, _
        BYVAL stream AS DWORD _
     ) AS LONG
    
    DECLARE FUNCTION wfopen CDECL IMPORT "msvcrt.dll" ALIAS "_wfopen" ( _
        BYREF filename AS WSTRINGZ, _
        BYREF mode AS WSTRINGZ _
     ) AS DWORD
    
     DECLARE FUNCTION fflush CDECL IMPORT "msvcrt.dll" ALIAS "fflush" ( _
        BYVAL fileStream AS DWORD _
    ) AS LONG
    
    GLOBAL g_pDesktopFolder AS IShellFolder
    GLOBAL g_stdout AS DWORD
    
    FUNCTION CHECK_HR(BYVAL hr AS LONG, BYVAL text AS WSTRING) AS LONG
        LOCAL isGood AS LONG
        isGood = SUCCEEDED(hr)
        IF ISFALSE(isGood) THEN
            PrintString(text & " failed with hresult "$$ & HEX$(hr, 8))
        END IF
        FUNCTION = isGood
    END FUNCTION
    
    FUNCTION FATAL_HR(BYVAL hr AS LONG, BYVAL text AS WSTRING) AS LONG
        LOCAL isGood AS LONG
        isGood = SUCCEEDED(hr)
        IF ISFALSE(isGood) THEN
            PrintString(text & " failed with hresult "$$ & HEX$(hr, 8))
            ExitProcess(hr)
        END IF
        FUNCTION = isGood
    END FUNCTION
    
    SUB DebugString(BYVAL str AS WSTRING)
        OutputDebugString(str & $$CRLF)
    END SUB
    
    SUB PrintString(BYVAL str AS WSTRING)
        fputws(str & $$CRLF, g_stdout)
        fflush(g_stdout)
        DebugString(str)
    END SUB
    
    FUNCTION PBMain()
    
        LOCAL inputFile, outputFile, shouldUnzip AS WSTRING
        LOCAL inputFileAttributes AS DWORD
    
        ' Fake being a console program
        AttachConsole(-1)  
        g_stdout = wfopen("CONOUT$"$$, "wt"$$)
    
        inputFile = COMMAND$(1)
        outputFile = COMMAND$(2)
        shouldUnzip = COMMAND$(3)
    
        inputFileAttributes = GetFileAttributes(BYVAL STRPTR(inputFile))
    
        IF (inputFile = "") OR (outputFile = "") OR (inputFileAttributes = &H0FFFFFFFF) THEN
                PrintString("Usage: ZipAThing C:\Thing\To\Zip output.zip <-u>"$$ & $$CRLF & _
                            "Thing may be a single file or a folder to zip."$$ & $$CRLF & _
                            "If a folder, everything inside it will be added"$$ & $$CRLF & $$CRLF & _
                            "If a third argument is present, the first argument must be a zip file"$$ & $$CRLF & _
                            "And the second a folder (doesn't have to exist) where the zip will be unzipped"$$ & $$CRLF _
                )
                EXIT FUNCTION
        END IF
    
        SHGetDesktopFolder(g_pDesktopFolder)
    
        inputFile = FullyQualifyFileName(BYVAL STRPTR(inputFile))
        outputFile = FullyQualifyFileName(BYVAL STRPTR(outputFile))
    
        PrintString(USING$("Input location is & &Output location is &"$$, inputFile, $$LF, outputFile))
    
        IF shouldUnzip = "" THEN
            DoZipStuff(inputFile, outputFile, inputFileAttributes AND %FILE_ATTRIBUTE_DIRECTORY)
        ELSE
            DoUnzipStuff(inputFile, outputFile)
        END IF
    
        g_pDesktopFolder = NOTHING
    
    END FUNCTION
    
    SUB DoZipStuff(BYVAL inputFile AS WSTRING, BYVAL outputFile AS WSTRING, BYVAL isZippingAFolder AS LONG)
    
        outputFile = MakeSureOfAZipExtension(outputFile)
    
        ' Overwrite the zip file
        KILL outputFile
        OPEN outputFile FOR OUTPUT AS #1
        CLOSE #1
    
        ' Turn our filenames to pidls
        LOCAL srcPidl, dstPidl AS ITEMIDLIST PTR
        srcPidl = ILCreateFromPath(BYVAL STRPTR(inputFile))
        dstPidl = ILCreateFromPath(BYVAL STRPTR(outputFile))
    
        ' Get an interface so we can create files and folders in the zip file
        LOCAL pZipFolder AS IStorage
        FATAL_HR(g_pDesktopFolder.BindToStorage(dstPidl, NOTHING, $IID_IStorage, pZipFolder), "Zip folder initial bind"$$)
    
        ' See sub comments
        SwapDodgyPidlFuncs()
    
        ' Source is a directory, zip the stuff in it
        IF isZippingAFolder THEN
    
            LOCAL pRootFolder AS IShellFolder  
    
            DebugString("Compressing a directory"$$)
            FATAL_HR(g_pDesktopFolder.BindToObject(srcPidl, NOTHING, $IID_IShellFolder, pRootFolder), "Source ShellFolder bind"$$)
            ZipAFolder(pRootFolder, pZipFolder, PATHNAME$(NAME, inputFile), outputFile)
            ' pZipFolder is released inside ZipAFolder
            pRootFolder = NOTHING
    
        ELSE 'Source is just a single file, just zip that
    
            LOCAL pSourceFileFolder AS IStorage
            LOCAL pSrcFile AS IStream
            LOCAL pFileName AS WSTRINGZ PTR
            LOCAL pNotNeeded AS ITEMIDLIST PTR
    
            DebugString("Compressing a file"$$)
    
            pFileName = PathFindFileName(BYVAL STRPTR(inputFile))
    
            FATAL_HR(SHBindToParent(srcPidl, $IID_IStorage, pSourceFileFolder, pNotNeeded), "Source file's folder bind"$$)
            FATAL_HR(pSourceFileFolder.MoveElementTo(@pFileName, pZipFolder, @pFileName, %STGMOVE_COPY), "Zipping single file"$$)
    
            PrintString("Zipped "$$ & @pFileName)
    
            pSourceFileFolder = NOTHING
            pZipFolder = NOTHING ' This is required, otherwise the temp files will never be deleted
        END IF
    
        PrintString("Finished"$$)
    
        CoTaskMemFree(srcPidl)
        CoTaskMemFree(dstPidl)
    
    END SUB
    
    FUNCTION CreateACreateBindContext() AS IBindCtx
    
        LOCAL pCtx AS IBindCtx
        LOCAL hr AS LONG
    
        IF CHECK_HR(CreateBindCtx(0, pCtx), "Creating BindCtx"$$) = %FALSE THEN EXIT FUNCTION
    
        LOCAL bo AS BIND_OPTS
        bo.cbStruct = SIZEOF(bo)
        bo.grfMode = %STGM_CREATE
        CHECK_HR(pCtx.SetBindOptions(bo), "Setting create bind option"$$)
        FUNCTION = pCtx
    
    END FUNCTION
    
    FUNCTION PidlToFolderName(pFolder AS IShellFolder, BYVAL pFilePidl AS ITEMIDLIST PTR) AS DWORD
    
        LOCAL pItemName AS DWORD
        LOCAL strName AS STRRET
        LOCAL hr AS LONG
    
        CHECK_HR(pFolder.GetDisplayNameOf(pFilePidl, %SHGDN_INFOLDER, strName), "Getting Pidl Name"$$)
        StrRetToStr(strName, pFilePidl, pItemName)
        FUNCTION = pItemName
    
    END FUNCTION
    
    SUB ZipAFolder(pFolder AS IShellFolder, pZipFolder AS IStorage, BYREF pFolderName AS WSTRINGZ, pFullZipFolderPath AS WSTRING)
    
        LOCAL pEnum AS IEnumIDList
        LOCAL hr AS LONG
        LOCAL itemsGot AS DWORD
        LOCAL pFilePidl AS ITEMIDLIST PTR
    
        PrintString(USING$("Zip: entering directory & (&)"$$, pFolderName, pFullZipFolderPath))
    
        ' First enumerate the files in this folder
        IF CHECK_HR(pFolder.EnumObjects(0, %SHCONTF_NONFOLDERS OR %SHCONTF_INCLUDEHIDDEN OR %SHCONTF_INCLUDESUPERHIDDEN, pEnum), "Enum folder files"$$) THEN
    
            LOCAL pSourceFolderStorage AS IStorage
            pSourceFolderStorage = pFolder
    
            WHILE pEnum.Next(1, pFilePidl, itemsGot) = %S_OK AND itemsGot = 1
    
                DebugString("Got file pidl "$$ & HEX$(pFilePidl))
    
                ' get the file name
                LOCAL pFileName AS WSTRINGZ PTR
                pFileName = PidlToFolderName(pFolder, pFilePidl)
    
                DebugString("About to zip "$$ & @pFileName)
                ' Copy it to the zip file
                hr = pSourceFolderStorage.MoveElementTo(@pFileName, pZipFolder, @pFileName, %STGMOVE_COPY)
                IF SUCCEEDED(hr) THEN
                    PrintString(USING$("Zipped &\&"$$, pFolderName, @pFileName))
                ELSE          
                    PrintString(USING$("Couldn't zip &\& because of error &"$$, pFolderName, @pFileName, HEX$(hr, 8)))              
                END IF
                CoTaskMemFree(pFilePidl)
                CoTaskMemFree(pFileName)
                itemsGot = 0
            WEND
            pSourceFolderStorage = NOTHING
            pEnum = NOTHING
        END IF
        ' we need to release all hold on the zip before moving onto sub or sibling folders
        ' This is because using the zip folder like this (with IStorage) marks the temp files it creates read-only
        ' but when you create folders, it doesn't mimic the folder structure you create (it just dumps all the temp files
        ' in a single, flat directory)
        ' so if there are files in sub/sibling directory with the same name (eg Dir1\photo.jpg and Dir2\photo.jpg)
        ' then Dir2\photo.jpg will fail to be added (error 80007005), because photo.jpg already exists from Dir1 and it can't be
        ' overwritten because it's marked read-only
        '
        ' This is why the enumeration is split into files (above) and folders (below)
        ' And why the parameter is relesed here
        pZipFolder = NOTHING
    
        IF CHECK_HR(pFolder.EnumObjects(0, %SHCONTF_FOLDERS OR %SHCONTF_INCLUDEHIDDEN OR %SHCONTF_INCLUDESUPERHIDDEN, pEnum), "Enum folder folders"$$) THEN
    
            LOCAL pCtx AS IBindCtx
            pCtx = CreateACreateBindContext()
    
            WHILE pEnum.Next(1, pFilePidl, itemsGot) = %S_OK AND itemsGot = 1
    
                ' Get an IShellFolder for this subfolder
                LOCAL pSourceSubFolder AS IShellFolder
                IF CHECK_HR(pFolder.BindToObject(pFilePidl, NOTHING, $IID_IShellFolder, pSourceSubFolder), "Bind to source subfolder"$$) THEN
    
                    ' create a sub folder in the zip file
                    '
                    ' we can't use pZipFolderStorage.CreateStorage like you normally would to create directories
                    ' since the Zip Folders CreateStorage does funky things like trying to compress the directory that it just created, which always fails.
                    ' so first we have to bind to a non-existant folder in the zip file
                    ' and create streams (aka files) through that (which then makes the folder exist!).
                    LOCAL subFolderName AS WSTRINGZ PTR
                    LOCAL fullZipSubFolderPath AS WSTRING
                    subFolderName = PidlToFolderName(pFolder, pFilePidl)
    
                    fullZipSubFolderPath = pFullZipFolderPath & "\" & @subFolderName
    
                    LOCAL zipSubFolderPidl AS ITEMIDLIST PTR
                    CHECK_HR(g_pDesktopFolder.ParseDisplayName(0, pCtx, BYVAL STRPTR(fullZipSubFolderPath), BYVAL %NULL, zipSubFolderPidl, BYVAL %NULL), "Parsing name of zip subfolder"$$)
    
                    LOCAL pZipSubFolder AS IStorage
                    hr = g_pDesktopFolder.BindToStorage(zipSubFolderPidl, pCtx, $IID_IStorage, pZipSubFolder)
                    IF CHECK_HR(hr, "Bind to zip subfolder"$$) THEN
    
                        ' recurse to the sub folder
                        ZipAFolder(pSourceSubFolder, pZipSubFolder, @subFolderName, fullZipSubFolderPath)
    
                    ELSE
    
                        PrintString(USING$("Couldn't create subfolder &\& in zip file. Error &"$$, pFolderName, subFolderName, HEX$(hr, 8)))
    
                    END IF
    
                    CoTaskMemFree(zipSubFolderPidl)
                    CoTaskMemFree(subFolderName)
                    pSourceSubFolder = NOTHING
                END IF
    
                CoTaskMemFree(pFilePidl)
                itemsGot = 0
            WEND
    
            pCtx = NOTHING
            pEnum = NOTHING
        END IF
    
        PrintString("Zip: leaving directory "$$ & pFolderName)
    
    END SUB
    
    SUB DoUnZipStuff(BYVAL inputFile AS WSTRING, BYVAL outputFile AS WSTRING)
    
        ' Don't bother checking the input is a zip file, so you could use the 'unzip'
        ' part of this program for generic folder copying!
        SHCreateDirectory(BYVAL %NULL, BYVAL STRPTR(outputFile))
    
        PrintString(USING$("Unzipping & to &"$$, inputFile, outputFile))
    
        ' Turn our filenames to pidls
        LOCAL srcPidl, dstPidl AS ITEMIDLIST PTR
        srcPidl = ILCreateFromPath(BYVAL STRPTR(inputFile))
        dstPidl = ILCreateFromPath(BYVAL STRPTR(outputFile))
    
        ' Get an interface so we can enum stuff in the zip file
        LOCAL pZipFolder AS IShellFolder
        FATAL_HR(g_pDesktopFolder.BindToObject(srcPidl, NOTHING, $IID_IShellFolder, pZipFolder), "Zip folder initial SF bind"$$)
    
        ' And one so we can create files and folders on disk
        LOCAL pFSFolder AS IStorage
        FATAL_HR(g_pDesktopFolder.BindToStorage(dstPidl, NOTHING, $IID_IStorage, pFSFolder), "Unzip dest folder bind"$$)
    
        UnzipAFolder(pZipFolder, pFSFolder, PATHNAME$(NAME, outputFile))
    
        PrintString("Finished"$$)
    
        pFSFolder = NOTHING
        pZipFolder = NOTHING
    
        CoTaskMemFree(srcPidl)
        CoTaskMemFree(dstPidl)
    
    END SUB
    
    SUB UnZipAFolder(pZipFolder AS IShellFolder, pFSFolder AS IStorage, BYREF pFolderName AS WSTRINGZ)
    
        LOCAL pEnum AS IEnumIDList
    
        PrintString(USING$("UnZip: entering directory &"$$, pFolderName))
    
        ' Only need one loop during unzipping, since this works how its supposed to
        IF CHECK_HR(pZipFolder.EnumObjects(0, _
                    %SHCONTF_NONFOLDERS OR %SHCONTF_FOLDERS OR %SHCONTF_INCLUDEHIDDEN OR %SHCONTF_INCLUDESUPERHIDDEN, _
                    pEnum _
                    ), "Enum folder files"$$ _
        ) THEN
    
            LOCAL itemsGot AS DWORD
            LOCAL pFilePidl AS ITEMIDLIST PTR
            LOCAL pZipFolderStorage AS IStorage
    
            pZipFolderStorage = pZipFolder
    
            WHILE pEnum.Next(1, pFilePidl, itemsGot) = %S_OK AND itemsGot = 1
    
                LOCAL attributes AS DWORD
                attributes = %SFGAO_STREAM
    
                DebugString("Got zip entry pidl "$$ & HEX$(pFilePidl))
    
                ' get the file name
                LOCAL pFileName AS WSTRINGZ PTR
                pFileName = PidlToFolderName(pZipFolder, pFilePidl)
    
                DebugString(USING$("About to unzip &, attributes &"$$, @pFileName, HEX$(attributes)))
    
                ' See if its a file or folder
                pZipFolder.GetAttributesOf(1, VARPTR(pFilePidl), attributes)
    
                IF (attributes AND %SFGAO_STREAM) THEN ' It's a file
    
                    LOCAL hr AS LONG
    
                    hr = pZipFolderStorage.MoveElementTo(@pFileName, pFSFolder, @pFileName, %STGMOVE_COPY)
                    IF SUCCEEDED(hr) THEN
                        PrintString(USING$("UnZipped &\&"$$, pFolderName, @pFileName))
                    ELSE          
                        PrintString(USING$("Couldn't unzip &\& because of error &"$$, pFolderName, @pFileName, HEX$(hr, 8)))              
                    END IF
    
                ELSE ' It's a folder, recurse
    
                    LOCAL pZipSubFolder AS IShellFolder
                    LOCAL pFSSubFolder AS IStorage
    
                    IF CHECK_HR(pZipFolder.BindToObject(pFilePidl, NOTHING, $IID_IShellFolder, pZipSubFolder), "Bind to zipped subfolder"$$) AND _
                        CHECK_HR(pFSFolder.CreateStorage(@pFileName, %STGM_CREATE, 0, 0, pFSSubFolder), "Create FS Subfolder"$$) THEN
    
                        UnZipAFolder(pZipSubFolder, pFSSubFolder, @pFileName)
    
                    END IF
    
                    pZipSubFolder = NOTHING
                    pFSSubFolder = NOTHING
    
                END IF
    
                CoTaskMemFree(pFilePidl)
                CoTaskMemFree(pFileName)
                itemsGot = 0
            WEND
            pZipFolderStorage = NOTHING
            pEnum = NOTHING
        END IF
    
        DebugString("Unzip: leaving directory "$$ & pFolderName)
    END SUB
    
    SUB SwapDodgyPidlFuncs
    
    ' Without swapping out SHILCreateFromPath and ILCreateFromPathW in zipfldr for something like SHSimpleIDListFromPath
    ' you can't use normal IStorage and IStream for zipping. With this change, you can. Which means you can know when
    ' the file actually finishes compression (since it happens synchronously when you release the interface)
    '
    ' Why is this needed? SHILCreateFromPath & ILCreateFromPath check that the path you give them exists before returning a pidl
    ' SHSimpleIDListFromPath doesn't. This 'non-check' is needed when creating new folders inside the zip file.
    ' They obviously don't exist before you create them (so you can't get a pidl) yet you need a pidl to create them!
    ' So chicken and the egg scenario. The swap thus allows us to create a pidl to something that doesn't exist
    ' before we actually make it exist.
    '
    ' Normally you'd use IStorage.CreateStorage to create subfolders in a directory, but they obviously have never
    ' tested that since it doesn't work in zip folders (or maybe its intentional sabotage, who knows?)
    '
    ' If the code makes your anti-virus scanner throw a wobbler, redirecting zipfldr's imports like this will probably be why
    
        LOCAL pZipDll, hShell32 AS DWORD
        LOCAL shilCreate, ilCreate AS DWORD
    
        pZipDll = GetModuleHandle("zipfldr.dll"$$)
        hShell32 = GetModuleHandle("shell32.dll"$$)
        shilCreate = GetProcAddress(hShell32, "SHILCreateFromPath")
        ilCreate = GetProcAddress(hShell32, "ILCreateFromPathW")
    
        DebugString(USING$("Found pZipFolder at &, shell32 at &"$$, HEX$(pZipDll), HEX$(hShell32)))
    
        LOCAL dirSize AS DWORD
        LOCAL pDescriptor AS IMAGE_IMPORT_DESCRIPTOR PTR
    
        pDescriptor = ImageDirectoryEntryToData(pZipDll, %TRUE, %IMAGE_DIRECTORY_ENTRY_IMPORT, dirSize)
    
        WHILE @pDescriptor.OriginalFirstThunk
    
            LOCAL pDllName AS ASCIIZ PTR
            pDllName = pZipDll + @pDescriptor.Name
    
            DebugString("Found imports from "$$ & @pDllName)
    
            IF UCASE$(@pDllName) = "SHELL32.DLL" THEN
    
                LOCAL seen AS LONG
                '
                ' Jose's headers have IMAGE_THUNK_DATA32 at 8 bytes,
                ' This causes the 'INCR pThunk' below to skip an import
                ' each time (since it's really supposed to be 4 bytes)
                ' LOCAL pThunk AS IMAGE_THUNK_DATA32 PTR
                '
                LOCAL pThunk AS DWORD PTR
                pThunk = pZipDll + @pDescriptor.FirstThunk
    
                WHILE @pThunk
    
                    LOCAL pThisImport, toSwapWith AS DWORD
                    pThisImport = @pThunk
    
                    IF pThisImport = shilCreate THEN
    
                        toSwapWith = CODEPTR(SHILCreateFromPath_Wrap)
                        DebugString(USING$("Replacing & (& - SHILCreateFromPath) with &"$$, HEX$(pThunk), HEX$(@pThunk), HEX$(toSwapWith)))
                        INCR seen
    
                    ELSEIF pThisImport = ilCreate THEN
    
                        toSwapWith = CODEPTR(ILCreateFromPath_Wrap) ' Could use SHSimpleIDListFromPath directly here but you can't codeptr imports :-(
                        DebugString(USING$("Replacing & (& - ILCreateFromPath) with &"$$, HEX$(pThunk), HEX$(@pThunk), HEX$(toSwapWith)))
                        INCR seen
    
                    END IF
                    IF toSwapWith THEN
    
                        LOCAL oldProt AS DWORD
                        VirtualProtect(pThunk, SIZEOF(pThisImport), %PAGE_EXECUTE_READWRITE, oldProt)
                        @pThunk = toSwapWith
                        VirtualProtect(pThunk, SIZEOF(pThisImport), oldProt, BYVAL 0)
                        FlushInstructionCache(GetCurrentProcess, pThunk, SIZEOF(pThisImport))
    
                        IF seen = 2 THEN EXIT SUB
    
                    END IF
                    INCR pThunk
                WEND          
            END IF
            INCR pDescriptor
        WEND
    END SUB
    
    FUNCTION SHILCreateFromPath_Wrap(BYREF pPath AS WSTRINGZ * 260, BYREF pPidl AS DWORD, BYREF attributes AS DWORD) COMMON STDCALL AS LONG
        pPidl = SHSimpleIDListFromPath(pPath)
        Function = %S_OK
    END FUNCTION
    
    FUNCTION ILCreateFromPath_Wrap(BYREF pPath AS WSTRINGZ * 260) COMMON STDCALL AS DWORD
        Function = SHSimpleIDListFromPath(pPath)
    END FUNCTION
    
    FUNCTION FullyQualifyFileName(BYREF pFileName AS WSTRINGZ) AS WSTRING
    
        LOCAL fullPath AS WSTRING
        LOCAL req AS DWORD
    
        IF PathIsRelative(pFileName) THEN
    
            req = GetFullPathName(pFileName, 0, BYVAL 0, BYVAL %NULL)
            fullPath = NUL$(req)
            LOCAL pFullPath AS WSTRINGZ PTR
            pFullPath = STRPTR(fullPath)
            GetFullPathName(pFileName, req, @pFullPath, BYVAL %NULL)
            fullPath = RTRIM$(fullPath, $NUL)
    
        ELSE
    
            fullPath = pFileName
    
        END IF
        FUNCTION = fullPath
    END FUNCTION
    
    FUNCTION MakeSureOfAZipExtension(BYVAL outName AS WSTRING) AS WSTRING
    
        LOCAL e AS WSTRING
        e = PATHNAME$(EXTN, outName)
    
        ' Zipfldr won't be loaded unless it has a .zip extenstion
        If UCASE$(e) <> ".ZIP"$$ Then
    
            outName &= ".zip"
    
        END IF
    
        FUNCTION = outName
    END FUNCTION
    Bug not worked around - on XP zipfldr never deletes the temporary files it creates during zipping, and there's no way to ask what the temporary path being used is so you can't do it easily yourself. It only deletes them (on XP) if you make it create an IShellView and send it the right messages, and I can't be bothered doing that. It was fixed n Vista
Working...
X