lucky_simFR
lucky_simFR

Reputation: 45

Excel VBA extract a specific file from zip

I have this code that let's the user select multiple zip files and it will copy all files containing the word "unformatted" in it's name and put it in a folder selected by the user. I don't understand why it doesn't copy to the folder.

thank you for your help

Option Explicit

Sub ExtractUnformattedFilesFromZips()
    'Ask user to select one or more zip files to extract from
    Dim ZipFiles As Variant
    ZipFiles = Application.GetOpenFilename(FileFilter:="Zip Files (*.zip), *.zip", Title:="Select one or more zip files to extract from", MultiSelect:=True)
    
    'Ask user to select output folder where "Unformatted" folder will be created
    Dim OutputFolder As String
    With Application.fileDialog(msoFileDialogFolderPicker)
        .Title = "Select output folder where Unformatted folder will be created"
        .Show
        If .SelectedItems.Count = 1 Then
            OutputFolder = .SelectedItems(1)
        Else
            Exit Sub 'User cancelled or selected more than one folder
        End If
    End With
    
    'Create Unformatted folder in the output folder
    On Error Resume Next 'Avoid error if Unformatted folder already exists
    MkDir OutputFolder & "\Unformatted"
    On Error GoTo 0
    
    'Loop through each selected zip file and extract files with "unformatted" in the name to the Unformatted folder
    Dim ZipFilePath As Variant
    Dim UnformattedFolderPath As String
    UnformattedFolderPath = OutputFolder & "\Unformatted\"
    Dim FileInZip As Variant
    Dim ExtractPath As String
    For Each ZipFilePath In ZipFiles
        If ZipFilePath <> False Then 'User didn't cancel selection
            ExtractPath = OutputFolder & "\" & Left$(ZipFilePath, Len(ZipFilePath) - 4) & "\" 'Create subfolder with the same name as the zip file
            On Error Resume Next 'Avoid error if subfolder already exists
            MkDir ExtractPath
            On Error GoTo 0
            Debug.Print "Extracting from " & ZipFilePath & " to " & ExtractPath
            With CreateObject("Shell.Application").Namespace(ZipFilePath)
                For Each FileInZip In .Items
                    If InStr(1, FileInZip.Name, "unformatted", vbTextCompare) > 0 Then 'File name contains "unformatted"
                        .CopyHere FileInZip, 16 'Extract file to output folder without prompt and overwrite existing file
                        Debug.Print "Extracting " & FileInZip.Name & " from " & ZipFilePath & " to " & UnformattedFolderPath
                        .CopyHere FileInZip, 256 'Extract file to Unformatted folder without prompt and overwrite existing file
                        Debug.Print "Extracting " & FileInZip.Name & " from " & ZipFilePath & " to " & ExtractPath
                    End If
                Next
            End With
        End If
    Next
    
    'Display message box indicating completion
    MsgBox "Extraction complete.", vbInformation
    
End Sub


Upvotes: 1

Views: 1682

Answers (2)

cyberponk
cyberponk

Reputation: 1766

I built another general purpose function for extracting files from ZIP files.

You can extract all the ZIP contente or specific files or files within specific paths.

'Extracts files from a zip file to a folder, with possibility to specify which files to extract
' ARGUMENTS:
'   * ZipFilePath = the path to the ZIP file
'
'   * OutputFolder = the path where to extract files
'
'   * SpecificFiles (OPTIONAL) = list of specific files to extract. If omitted, all files will be extracted.
'        Can contain folder paths to specify an exact file inside the zip.
'        Does not work with wildcards.
'        Examples:
'           filetest.txt  -> this will extract any file with this name in any subfolder
'           \filetest.txt -> this will only extract filetest.txt if it is in the root folder
'           \test\filetest.txt -> this will only extract filetest.txt if it is in the 'test' folder
'

'   * Options (FLAGS) = Options for extraction. To add options, use + operator (ex. 4+1024)
'        Some of these options only work when SpecificFiles is omitted

'        (4)    Do not display a progress dialog box.
'        (8)    Give the file being operated on a new name in a move, copy, or rename operation if
'               a file with the target name already exists.
'        (16)   Respond with "Yes" / "Yes to All" for any dialog box that is displayed.
'        (64)   Preserve undo information, if possible.
'        (128)  Perform the operation on files only if a wildcard file name (*.*) is specified.
'        (256)  Display a progress dialog box but do not show the file names.
'        (512)  Do not confirm the creation of a new directory if the operation requires one to be created.
'        (1024) Do not display a user interface if an error occurs.
'        (2048) Version 4.71. Do not copy the security attributes of the file.
'        (4096) Only operate in the local directory. Do not operate recursively into subdirectories.
'        (8192) Version 5.0. Do not copy connected files as a group. Only copy the specified files.
'
'   do_not_use = this variable is used for recursion, to pass the current folder
Function Extract_from_zip_file(ZipFilePath As Variant, OutputFolder As Variant, Optional ByRef SpecificFiles As collection = Nothing, Optional Options As Variant = 0, Optional do_not_use As String = "") As Boolean
    Dim res As Boolean
    
    'These objects require adding a reference to "Microsoft Shell Controls And Automation" in Tools / References menu
    'Dim sh As New shell
    'Dim item As Shell32.FolderItem
    'Dim ZipFolder As Shell32.Folder
    
    'As an alternative, without adding reference (but without intellisense)
    'This also requires ZipFilePath and OutputFolder to be declared as Variant instead of String
    Dim sh
    Dim item
    Dim ZipFolder
    Set sh = CreateObject("Shell.Application")
    
    'Namespace returns a Shell32.Folder object
    '   Reference: https://learn.microsoft.com/en-us/windows/win32/shell/folder
    Set ZipFolder = sh.Namespace(ZipFilePath)
    If ZipFolder Is Nothing Then
        'Zip file does not exist or is invalid
        Exit Function
    End If
    
    If SpecificFiles Is Nothing Then
        'No specific file was requested, so we can extract all, using the easy way
        
        MkDir_recursive OutputFolder
        'Reference: https://learn.microsoft.com/en-us/windows/win32/shell/folder-copyhere
        sh.Namespace(OutputFolder).CopyHere ZipFolder.items, Options
        Extract_from_zip_file = True
        Exit Function
    Else
        'Specific files were requested
    
        'FolderItems is a Shell32.FolderItems object
        '   Reference: https://learn.microsoft.com/en-us/windows/win32/shell/folderitems
        For Each item In ZipFolder.items
            'Item is a Shell32.FolderItem object
            '   Reference: https://learn.microsoft.com/en-us/windows/win32/shell/folderitem
            
            If item.IsFolder And (Options And 4096) = 0 Then
                'Is a folder. Do a recursive extraction, except if option 4096 is set
                'file = OutputFolder & IIf(Right(OutputFolder, 1) = "\", "", "\") & item.Name
                res = Extract_from_zip_file(item.path, OutputFolder & IIf(Right(OutputFolder, 1) = "\", "", "\") & item.Name, SpecificFiles, Options, do_not_use & "\" & item.Name)
                If res = False Then Exit Function
            Else
                'Is a file, check if is listed for extraction
                'The list allows for relative paths inside the zip file. Ex: folder1\file.exe
                If Collection_Contains_String(SpecificFiles, item.Name) Or _
                   Collection_Contains_String(SpecificFiles, do_not_use & "\" & item.Name) Or _
                   Collection_Contains_String(SpecificFiles, Replace(do_not_use, "\", "", 1, 1) & "\" & item.Name) Then
                    MkDir_recursive OutputFolder
                    sh.Namespace(OutputFolder).CopyHere item, Options
                End If
            End If
        Next
    End If
    Extract_from_zip_file = True
End Function

'Checks if a Collecion contains a string
Function Collection_Contains_String(ByRef collection As collection, value As String, Optional Case_sensitive As Boolean = False) As Boolean
    Dim i As Long
    For i = 1 To collection.count
        If VarType(collection(i)) = 8 Then
            If (Case_sensitive And (collection(i) = value)) Or _
               (Not Case_sensitive And (LCase(collection(i)) = LCase(value))) Then
                Collection_Contains_String = True
                Exit Function
            End If
        End If
    Next
End Function

And an example of usage:

'Extract some specific files from
Dim coll As New collection
coll.Add "any_file_with_this_name.exe"
coll.Add "\specific_file_in_root_folder.exe"
coll.Add "\TEST\specific_file_in_TEST_folder.exe"

Call Extract_from_zip_file("c:\MyZipFile.zip", "c:\ExtractTo", coll)

'Extract all files
Call Extract_from_zip_file("c:\MyZipFile.zip", "c:\ExtractTo")

Upvotes: 0

Tim Williams
Tim Williams

Reputation: 166755

This worked for me:

Sub ExtractUnformattedFilesFromZips()
    
    Dim ZipFiles As Variant, ZipFilePath As Variant, UnformattedFolderPath As Variant
    Dim FileInZip As Variant, ExtractPath As Variant, OutputFolder As Variant
    Dim haveDir As Boolean, oApp As Object
    
    ZipFiles = Application.GetOpenFilename(FileFilter:="Zip Files (*.zip), *.zip", _
           Title:="Select one or more zip files to extract from", MultiSelect:=True)
    If Not IsArray(ZipFiles) Then Exit Sub
    
    OutputFolder = UserSelectFolder( _
         "Select output folder where Unformatted folder will be created")
    If Len(OutputFolder) = 0 Then Exit Sub
    UnformattedFolderPath = OutputFolder & "\Unformatted\"
    EnsureDir UnformattedFolderPath
    
    Set oApp = CreateObject("Shell.Application")
    For Each ZipFilePath In ZipFiles
        
        haveDir = False 'reset flag
        Debug.Print "Extracting from " & ZipFilePath & " to " & ExtractPath
        
        With oApp.Namespace(ZipFilePath)
            For Each FileInZip In .Items
                If InStr(1, FileInZip.Name, "unformatted", vbTextCompare) > 0 Then 'File name contains "unformatted"
                    If Not haveDir Then 'already have an output folder for this zip?
                        ExtractPath = UnformattedFolderPath & BaseName(ZipFilePath)
                        EnsureDir ExtractPath
                        haveDir = True
                    End If
                    Debug.Print , FileInZip
                    oApp.Namespace(ExtractPath).CopyHere FileInZip, 256
                End If
            Next
        End With
    Next
    MsgBox "Extraction complete.", vbInformation
End Sub

'Ask user to select a folder
Function UserSelectFolder(sPrompt As String) As String
    With Application.FileDialog(msoFileDialogFolderPicker)
        .AllowMultiSelect = False
        .Title = sPrompt
        If .Show = -1 Then UserSelectFolder = .SelectedItems(1)
    End With
End Function

'Make sure a folder exists
Sub EnsureDir(dirPath)
    If Len(Dir(dirPath, vbDirectory)) = 0 Then
        MkDir dirPath
    End If
End Sub

'get a filename without extension
Function BaseName(sName)
    BaseName = CreateObject("scripting.filesystemobject").getbasename(sName)
End Function

Upvotes: 1

Related Questions