Share via


Extract file from archive with progress (VB.NET)

Introduction

The System.IO.Compression namespace contains classes for compressing and decompressing files and streams which can also use these types to read and modify the contents of a compressed file. Microsoft provides easy to follow examples on the following page. This article will provide base code to provide progress while extracting files from a compressed file using a ProgressBar or hooks to use for displaying the file being extracted name and file size with room to modified for personal requirements. Base code is used rather than a full fledge example as this leaves room for personal business requirements.

Laying a foundation

To provides the ability to rely progress to the user interface the following delegate is required which the form can subscribe too which is explained below.

Public Delegate  Sub ZipEventHandler(sender As Object, e As  ZipArgs)

The following class is used by the delegate above to provide current file name, current file size, percent done from total files in the compress file and a boolean to indicate if the current file was extracted.

Namespace Classes
    ''' <summary>
    ''' For ZipEventHandler delegate args
    ''' </summary>
    Public Class  ZipArgs
        Inherits EventArgs
 
        Private _fileName As String
        Private _percentDone As Integer
        Private _fileLength As Long
        Private _extracted As Boolean
 
        Public Sub  New(
           fileName As  String,
           fileLength As  Long,
           percentDone As  Integer,
           extracted As  Boolean)
 
            _fileName = fileName
            _fileLength = fileLength
            _percentDone = percentDone
            _extracted = extracted
        End Sub
        Public ReadOnly  Property FileName As String
            Get
                Return _fileName
            End Get
        End Property
        Public ReadOnly  Property FileLength() As Long
            Get
                Return _fileLength
            End Get
        End Property
        Public ReadOnly  Property PercentDone As Integer
            Get
                Return _percentDone
            End Get
        End Property
        Public ReadOnly  Property Extracted() As Boolean
            Get
                Return _extracted
            End Get
        End Property
    End Class
End Namespace

In the form which will perform file extraction from a zip file, the delegate ZipEventHandler is setup by viewing the form in the Visual Studio code editor, selecting (Form1 events) in the center combo box of the editor then selecting ZipEventHandler in the right combo box which creates the following line of code.

Public Event  ZipEventHandler As  ZipEventHandler

Form code

Selecting a compressed file

A standard OpenFileDialog is used to allow the selection of a file. when a selection is made the path and file name are placed into a readonly TextBox.

Selecting a folder for file extraction

A custom Folder dialog is used to prompt for a folder location to extract files too. Before building the project right click on solution explorer and select restore NuGet packages. The custom folder dialog will work the same as a standard dialog with a better view and more options.

Purging extracted files

Before getting into extracting files, during extraction if a file already exists a IOException will be thrown. In this code sample logic will bypass existing files as this is a base code sample. If overwriting is permitted a boolean may be added to the function to extract files to permit deletion or similar logic. 

See code in the purge button which provide code to delete all files in the selected extract folder.

Extracting files from archive

The following function signature accepts a zip file name with path, a folder to extract files, optionally a ProgressBar

Private Async Function ExtractAllAsync(
    zipFileName As  String,
    extractPath As  String,
    Optional progressBar As ProgressBar = Nothing) As  Task

All operations are wrapped in a try/multiple catch to focus on IO exception and then general exceptions. Note in the above signature ExtraxtAllAsync is a function with Task as a return type but does not actually return anything as using a sub/procedure is not wise with asynchronous operations as any exception may get swallowed.

Note to use ZipArchive add a reference to System.IO.Compression.dll and System.IO.Compression.FileSystem.dll

The first operation is to open the zip file

Using zipZrchive As  ZipArchive = ZipFile.OpenRead(zipFileName)

Next assertion is done to determine if a ProgressBar was passed (see code inline).

If progressBar IsNot Nothing Then
    progressBar.Minimum = 0
    progressBar.Maximum = zipZrchive.Entries.Count
End If

Once finished the following code handles iterating each file in the file passed into the function.

Await Task.Run(
Sub()
 
    Dim count As Integer  = zipZrchive.Entries.Count
    Dim currentFileName As String  = ""
    Dim currentFileLength As Long  = 0
 
    '
    ' Iterate files in compressed file
    '
    For index As Integer  = 0 To  count - 1
        Try
 
            Dim entry As ZipArchiveEntry = zipZrchive.Entries(index)
            currentFileName = entry.FullName
 
            '
            ' Bypass existing file, optional logic can be used
            ' to overwrite an existing file
            '
            If Not  File.Exists(Path.Combine(extractPath, entry.FullName)) Then
                entry.ExtractToFile(Path.Combine(extractPath, entry.FullName))
                currentFileLength = entry.Length
                extractCount += 1
                extracted = True
            Else
                extracted = False
            End If
 
        Catch ioex As IOException
            '
            ' By removing the logic above for File.Exists an exception
            ' would be thrown. To get around this the file must first
            ' be removed
            '
            If ioex.Message.EndsWith("already exists.") Then
                skipCount += 1
            Else
                errorCount += 1
            End If
        Catch ex As Exception
            '
            ' Unknown error
            ' Should not be ignored, for a production app
            ' consider writing to a log file
            '
            errorCount += 1
        End Try
 
 
        progressValue += 1
 
        If progressBar IsNot Nothing Then
            Invoke(Sub() progressBar.Value += 1)
        End If
 
        Task.Delay(100).Wait() ' REMOVE FOR PRODUCTION
 
        '
        ' Pass current file name without path,
        ' file size of current file,
        ' percent done and if the file was extracted
        '
        ' Room here for customizing what is passed
        ' by modifying ZipArgs class.
        '
        RaiseEvent ZipEventHandler(
                       Me,
                       New ZipArgs(
                           currentFileName,
                           currentFileLength,
                           progressValue.PercentageOf(count),
                           extracted))
 
    Next
 
End Sub)

 
Key points in the code above

  • entry.FullName only has the file name, extractPath contains the path selected in the folder dialog (see inline code for combining path and file name).
  • To prevent IOException the following code protects against this but in this code sample assertion is used to prevent this from happening.
  • The following code sets the value for progress to the ProgressBar if a ProgressBar is passed.
  • For demonstration purposes the following code wait has been added in the event there are only a few files so to slow down the process, remove for production.
  • The RaiseEvent is pushes elements to the listener (see inline code) which in turn pushes information to a ProgressBar is passed and also to add extracted file names and file sizes to a ListBox.

Form load code

This code is where initialization is performed e.g. set default paths for extracting files.

Summary

Code has been provided to learn how to show progress while extracting files from a zip file leaving room for customization. A more advance code sample would be counterproductive in both learning and modifying for personal use which is why the code sample is basic. Although the code is basic there are many parts e.g. asynchronous code and custom events which will be new to many developers that will take time to learn. The best way to learn when code does not make sense is to set breakpoints, run code and step through the code to examine how lines of code perform.

See also

Source code

See the following GitHub repository.