Skip to main content

.NET C#/VB -
Read & Write Compressed Files

Sections:

C#

// AZUL CODING ---------------------------------------
// .NET C#/VB - Read & Write Compressed Files
// https://youtu.be/vbSbi196s58


using SharpCompress.Archives;
using SharpCompress.Common;

namespace AzulArchives;

class Program
{
    static void Main(string[] args)
    {
        // Install the SharpCompress package first:
        // dotnet add package SharpCompress

        bool running = true;
        while (running)
        {
            Console.WriteLine("\n Archive manager");
            Console.WriteLine(new string('─', 70));
            Console.WriteLine(" 1. Create archive from folder");
            Console.WriteLine(" 2. Extract archive");
            Console.WriteLine(" 3. Display archive contents");
            Console.WriteLine(" 4. Read file from archive");
            Console.WriteLine(" 5. Add file to archive");
            Console.WriteLine(" 6. Remove file from archive");
            Console.WriteLine(" 7. Exit");
            Console.WriteLine(new string('─', 70));

            string choice = GetInput(" Enter your choice >> ");
            try
            {
                switch (choice)
                {
                    case "1":
                        CreateArchiveFromFolder(GetInput(" Enter folder path >> "));
                        break;
                    case "2":
                        ExtractArchive(GetInput(" Enter archive path >> "));
                        break;
                    case "3":
                        DisplayArchiveContents(GetInput(" Enter archive path >> "));
                        break;
                    case "4":
                        ReadFileFromArchive(
                            GetInput(" Enter archive path >> "),
                            GetInput(" Enter filename to read >> ")
                        );
                        break;
                    case "5":
                        WriteFileToArchive(
                            GetInput(" Enter archive path >> "),
                            GetInput(" Enter filename to add >> ")
                        );
                        break;
                    case "6":
                        DeleteFileFromArchive(
                            GetInput(" Enter archive path >> "),
                            GetInput(" Enter filename to remove >> ")
                        );
                        break;
                    case "7":
                        running = false;
                        break;
                    default:
                        Console.ForegroundColor = ConsoleColor.Red;
                        Console.WriteLine(" Invalid choice. Please try again.");
                        Console.ResetColor();
                        continue;
                }

                if (running)
                {
                    Console.WriteLine("\n Press any key to continue...");
                    Console.ReadKey();
                }
            }
            catch (Exception ex)
            {
                Console.ForegroundColor = ConsoleColor.Red;
                Console.WriteLine($"\n Error: {ex.Message}");
                Console.ResetColor();
                Console.WriteLine("\n Press any key to continue...");
                Console.ReadKey();
            }
        }
    }

    #region Extract and compress

    static void CreateArchiveFromFolder(string folderPath)
    {
        if (!Directory.Exists(folderPath))
            throw new DirectoryNotFoundException("Folder does not exist.");

        string dest = $"{folderPath}.zip";

        using var archive = ArchiveFactory.Create(ArchiveType.Zip);
        archive.AddAllFromDirectory(folderPath);

        archive.SaveTo(dest, CompressionType.Deflate);
        Console.WriteLine($"\n Archive created: {dest}");
    }

    static void ExtractArchive(string archivePath)
    {
        using var archive = ArchiveFactory.Open(archivePath);
        string destDir = Path.Combine(
            Path.GetDirectoryName(archivePath) ?? "",
            Path.GetFileNameWithoutExtension(archivePath)
        );

        Directory.CreateDirectory(destDir);

        foreach (var entry in archive.Entries.Where(entry => !entry.IsDirectory))
        {
            entry.WriteToDirectory(destDir, new ExtractionOptions
            {
                ExtractFullPath = true,
                Overwrite = true
            });
        }
        Console.WriteLine($"\n Extracted to folder: {destDir}");
    }

    #endregion
    #region Read contents

    static void DisplayArchiveContents(string archivePath)
    {
        using var archive = ArchiveFactory.Open(archivePath);
        List<IArchiveEntry> entries = [.. archive.Entries.Where(e => !e.IsDirectory)];

        Console.WriteLine(
            $"\n {Path.GetFileName(archivePath)} • {archive.Type} • Total files: {entries.Count}"
        );
        Console.WriteLine(new string('─', 70));
        Console.WriteLine($"{" Filename",-35} {"Size",-12} {"Compressed",-12} Ratio");
        Console.WriteLine(new string('─', 70));

        long totalSize = 0;
        long totalCompressed = 0;

        var groupedEntries = entries
            .Select(e => new
            {
                Entry = e,
                Directory = Path.GetDirectoryName(e.Key)?.Replace("\\", "/") ?? "",
                FileName = Path.GetFileName(e.Key)
            })
            .OrderBy(e => e.Directory)
            .ThenBy(e => e.FileName)
            .ToList();

        string? currentDir = null;
        foreach (var item in groupedEntries)
        {
            totalSize += item.Entry.Size;
            totalCompressed += item.Entry.CompressedSize;

            if (item.Directory != currentDir)
            {
                if (!string.IsNullOrEmpty(item.Directory))
                {
                    Console.ForegroundColor = ConsoleColor.Blue;
                    Console.WriteLine($"\n📁 {item.Directory}/");
                    Console.ResetColor();
                }
                currentDir = item.Directory;
            }

            string indent = string.IsNullOrEmpty(item.Directory) ? "" : "   ";
            string? filename = item.FileName;

            long uncompressedSize = item.Entry.Size;
            long compressedSize = item.Entry.CompressedSize;
            double ratio = 0;
            if (uncompressedSize > 0)
                ratio = (1 - (double)compressedSize / uncompressedSize) * 100;

            Console.Write($"{indent}📄 ");

            if (string.IsNullOrEmpty(indent))
                Console.Write($"{filename,-33}");
            else
                Console.Write($"{filename,-30}");

            Console.ForegroundColor = ConsoleColor.Cyan;
            Console.Write($"{FormatSize(uncompressedSize),-13}");

            Console.ForegroundColor = ConsoleColor.Magenta;
            Console.Write($"{FormatSize(compressedSize),-13}");

            Console.ForegroundColor = ConsoleColor.Yellow;
            Console.WriteLine($"{ratio:F1}%");
            Console.ResetColor();
        }

        Console.WriteLine(new string('─', 70));
        Console.Write($"{" Total",-36}");

        Console.ForegroundColor = ConsoleColor.Cyan;
        Console.Write($"{FormatSize(totalSize),-13}");

        Console.ForegroundColor = ConsoleColor.Magenta;
        Console.Write($"{FormatSize(totalCompressed),-13}");

        double totalRatio = totalSize > 0 ? (1 - (double)totalCompressed / totalSize) * 100 : 0;
        Console.ForegroundColor = ConsoleColor.Yellow;
        Console.WriteLine($"{totalRatio:F1}%");
        Console.ResetColor();
    }

    static void ReadFileFromArchive(string archivePath, string filePathInArchive)
    {
        using var archive = ArchiveFactory.Open(archivePath);
        var entry = archive.Entries.FirstOrDefault(e =>
            e.Key?.Equals(filePathInArchive, StringComparison.OrdinalIgnoreCase) ?? false)
                ?? throw new FileNotFoundException($"{filePathInArchive} not found in archive.");

        using var reader = new StreamReader(entry.OpenEntryStream());

        Console.WriteLine($" {Path.GetFileName(filePathInArchive)}");
        Console.WriteLine(new string('─', 70));
        Console.WriteLine(reader.ReadToEnd());
        Console.WriteLine(new string('─', 70));
    }

    #endregion
    #region Modify archive

    static void WriteFileToArchive(string archivePath, string filePathInArchive)
    {
        if (!File.Exists(filePathInArchive))
            throw new FileNotFoundException("Input file does not exist.");

        using var archive = ArchiveFactory.Open(archivePath);
        if (archive is IWritableArchive writable)
        {
            writable.AddEntry(Path.GetFileName(filePathInArchive), filePathInArchive);

            string tempFile = archivePath + ".tmp";
            writable.SaveTo(tempFile, DetectCompressionType(archive));

            archive.Dispose();
            File.Move(tempFile, archivePath, true);

            Console.WriteLine("\n File added to archive.");
        }
        else
        {
            throw new NotSupportedException("This is a read-only archive.");
        }
    }

    static void DeleteFileFromArchive(string archivePath, string filePathInArchive)
    {
        using var archive = ArchiveFactory.Open(archivePath);
        if (archive is IWritableArchive writable)
        {
            var entry = archive.Entries.FirstOrDefault(e =>
                e.Key?.Equals(filePathInArchive, StringComparison.OrdinalIgnoreCase) ?? false)
                    ?? throw new FileNotFoundException($"{filePathInArchive} not found in archive.");

            writable.RemoveEntry(entry);

            string tempFile = archivePath + ".tmp";
            writable.SaveTo(tempFile, DetectCompressionType(archive));

            archive.Dispose();
            File.Move(tempFile, archivePath, true);

            Console.WriteLine("\n File removed from archive.");
        }
        else
        {
            throw new NotSupportedException("This is a read-only archive.");
        }
    }

    #endregion
    #region Helper functions

    static string GetInput(string prompt)
    {
        Console.Write(prompt);

        string input = Console.ReadLine()?.Trim() ?? "";
        Console.WriteLine();

        return input;
    }

    static string FormatSize(long bytes)
    {
        string[] sizes = ["B", "KB", "MB", "GB"];
        double len = bytes;
        int order = 0;

        while (len >= 1024 && order < sizes.Length - 1)
        {
            order++;
            len /= 1024;
        }

        return $"{len:0.##} {sizes[order]}";
    }

    static CompressionType DetectCompressionType(IArchive archive)
    {
        return archive.Entries.FirstOrDefault(e => !e.IsDirectory)?.CompressionType
            ?? CompressionType.Deflate;
    }

    #endregion
}

Help support the channel

VB.NET

' AZUL CODING ---------------------------------------
' .NET C#/VB - Read & Write Compressed Files
' https://youtu.be/vbSbi196s58


Imports System
Imports System.IO
Imports SharpCompress.Archives
Imports SharpCompress.Common

Module Program
    Sub Main(args As String())
        ' Install the SharpCompress package first:
        ' dotnet add package SharpCompress

        Dim running As Boolean = True
        While running
            Console.WriteLine(vbLf & " Archive manager")
            Console.WriteLine(New String("─"c, 70))
            Console.WriteLine(" 1. Create archive from folder")
            Console.WriteLine(" 2. Extract archive")
            Console.WriteLine(" 3. Display archive contents")
            Console.WriteLine(" 4. Read file from archive")
            Console.WriteLine(" 5. Add file to archive")
            Console.WriteLine(" 6. Remove file from archive")
            Console.WriteLine(" 7. Exit")
            Console.WriteLine(New String("─"c, 70))

            Dim choice As String = GetInput(" Enter your choice >> ")
            Try
                Select Case choice
                    Case "1"
                        CreateArchiveFromFolder(GetInput(" Enter folder path >> "))
                    Case "2"
                        ExtractArchive(GetInput(" Enter archive path >> "))
                    Case "3"
                        DisplayArchiveContents(GetInput(" Enter archive path >> "))
                    Case "4"
                        ReadFileFromArchive(
                            GetInput(" Enter archive path >> "),
                            GetInput(" Enter filename to read >> ")
                        )
                    Case "5"
                        WriteFileToArchive(
                            GetInput(" Enter archive path >> "),
                            GetInput(" Enter filename to add >> ")
                        )
                    Case "6"
                        DeleteFileFromArchive(
                            GetInput(" Enter archive path >> "),
                            GetInput(" Enter filename to remove >> ")
                        )
                    Case "7"
                        running = False
                    Case Else
                        Console.ForegroundColor = ConsoleColor.Red
                        Console.WriteLine(" Invalid choice. Please try again.")
                        Console.ResetColor()
                        Continue While
                End Select

                If running Then
                    Console.WriteLine(vbLf & " Press any key to continue...")
                    Console.ReadKey()
                End If
            Catch ex As Exception
                Console.ForegroundColor = ConsoleColor.Red
                Console.WriteLine($"{vbLf} Error: {ex.Message}")
                Console.ResetColor()
                Console.WriteLine(vbLf & " Press any key to continue...")
                Console.ReadKey()
            End Try
        End While
    End Sub

#Region "Extract and compress"

    Sub CreateArchiveFromFolder(folderPath As String)
        If Not Directory.Exists(folderPath) Then
            Throw New DirectoryNotFoundException("Folder does not exist.")
        End If

        Dim dest As String = $"{folderPath}.zip"

        Using archive = ArchiveFactory.Create(ArchiveType.Zip)
            archive.AddAllFromDirectory(folderPath)
            archive.SaveTo(dest, CompressionType.Deflate)
        End Using
        
        Console.WriteLine($"{vbLf} Archive created: {dest}")
    End Sub

    Sub ExtractArchive(archivePath As String)
        Using archive = ArchiveFactory.Open(archivePath)
            Dim destDir As String = Path.Combine(
                If(Path.GetDirectoryName(archivePath), ""),
                Path.GetFileNameWithoutExtension(archivePath)
            )

            Directory.CreateDirectory(destDir)

            For Each entry In archive.Entries.Where(Function(e) Not e.IsDirectory)
                entry.WriteToDirectory(destDir, New ExtractionOptions With {
                    .ExtractFullPath = True,
                    .Overwrite = True
                })
            Next

            Console.WriteLine($"{vbLf} Extracted to folder: {destDir}")
        End Using
    End Sub

#End Region
#Region "Read contents"

    Sub DisplayArchiveContents(archivePath As String)
        Using archive = ArchiveFactory.Open(archivePath)
            Dim entries As List(Of IArchiveEntry) = archive.Entries.Where(Function(e) Not e.IsDirectory).ToList()

            Console.WriteLine(
                $"{vbLf} {Path.GetFileName(archivePath)} • {archive.Type} • Total files: {entries.Count}"
            )
            Console.WriteLine(New String("─"c, 70))
            Console.WriteLine($"{" Filename",-35} {"Size",-12} {"Compressed",-12} Ratio")
            Console.WriteLine(New String("─"c, 70))

            Dim totalSize As Long = 0
            Dim totalCompressed As Long = 0

            Dim groupedEntries = entries _
                .Select(Function(e) New With {
                    .Entry = e,
                    .Directory = If(Path.GetDirectoryName(e.Key)?.Replace("\", "/"), ""),
                    .FileName = Path.GetFileName(e.Key)
                }) _
                .OrderBy(Function(e) e.Directory) _
                .ThenBy(Function(e) e.FileName) _
                .ToList()

            Dim currentDir As String = Nothing
            For Each item In groupedEntries
                totalSize += item.Entry.Size
                totalCompressed += item.Entry.CompressedSize

                If item.Directory <> currentDir Then
                    If Not String.IsNullOrEmpty(item.Directory) Then
                        Console.ForegroundColor = ConsoleColor.Blue
                        Console.WriteLine($"{vbLf}📁 {item.Directory}/")
                        Console.ResetColor()
                    End If
                    currentDir = item.Directory
                End If

                Dim indent As String = If(String.IsNullOrEmpty(item.Directory), "", "   ")
                Dim filename As String = item.FileName

                Dim uncompressedSize As Long = item.Entry.Size
                Dim compressedSize As Long = item.Entry.CompressedSize
                Dim ratio As Double = 0
                If uncompressedSize > 0 Then
                    ratio = (1 - CDbl(compressedSize) / uncompressedSize) * 100
                End If

                Console.Write($"{indent}📄 ")

                If String.IsNullOrEmpty(indent) Then
                    Console.Write($"{filename,-33}")
                Else
                    Console.Write($"{filename,-30}")
                End If

                Console.ForegroundColor = ConsoleColor.Cyan
                Console.Write($"{FormatSize(uncompressedSize),-13}")

                Console.ForegroundColor = ConsoleColor.Magenta
                Console.Write($"{FormatSize(compressedSize),-13}")

                Console.ForegroundColor = ConsoleColor.Yellow
                Console.WriteLine($"{ratio:F1}%")
                Console.ResetColor()
            Next

            Console.WriteLine(New String("─"c, 70))
            Console.Write($"{" Total",-36}")

            Console.ForegroundColor = ConsoleColor.Cyan
            Console.Write($"{FormatSize(totalSize),-13}")

            Console.ForegroundColor = ConsoleColor.Magenta
            Console.Write($"{FormatSize(totalCompressed),-13}")

            Dim totalRatio As Double = If(totalSize > 0, (1 - CDbl(totalCompressed) / totalSize) * 100, 0)
            Console.ForegroundColor = ConsoleColor.Yellow
            Console.WriteLine($"{totalRatio:F1}%")
            Console.ResetColor()
        End Using
    End Sub

    Sub ReadFileFromArchive(archivePath As String, filePathInArchive As String)
        Using archive = ArchiveFactory.Open(archivePath)
            Dim entry = archive.Entries.FirstOrDefault(Function(e) _
                If(e.Key?.Equals(filePathInArchive, StringComparison.OrdinalIgnoreCase), False))
            
            If entry Is Nothing Then
                Throw New FileNotFoundException($"{filePathInArchive} not found in archive.")
            End If

            Using reader As New StreamReader(entry.OpenEntryStream())
                Console.WriteLine($" {Path.GetFileName(filePathInArchive)}")
                Console.WriteLine(New String("─"c, 70))
                Console.WriteLine(reader.ReadToEnd())
                Console.WriteLine(New String("─"c, 70))
            End Using
        End Using
    End Sub

#End Region
#Region "Modify archive"

    Sub WriteFileToArchive(archivePath As String, filePathInArchive As String)
        If Not File.Exists(filePathInArchive) Then
            Throw New FileNotFoundException("Input file does not exist.")
        End If

        Using archive = ArchiveFactory.Open(archivePath)
            Dim writable = TryCast(archive, IWritableArchive)
            If writable IsNot Nothing Then
                writable.AddEntry(Path.GetFileName(filePathInArchive), filePathInArchive)

                Dim tempFile As String = archivePath & ".tmp"
                writable.SaveTo(tempFile, DetectCompressionType(archive))

                archive.Dispose()
                File.Move(tempFile, archivePath, True)

                Console.WriteLine(vbLf & " File added to archive.")
            Else
                Throw New NotSupportedException("This is a read-only archive.")
            End If
        End Using
    End Sub

    Sub DeleteFileFromArchive(archivePath As String, filePathInArchive As String)
        Using archive = ArchiveFactory.Open(archivePath)
            Dim writable = TryCast(archive, IWritableArchive)
            If writable IsNot Nothing Then
                Dim entry = archive.Entries.FirstOrDefault(Function(e) _
                    If(e.Key?.Equals(filePathInArchive, StringComparison.OrdinalIgnoreCase), False))
                
                If entry Is Nothing Then
                    Throw New FileNotFoundException($"{filePathInArchive} not found in archive.")
                End If

                writable.RemoveEntry(entry)

                Dim tempFile As String = archivePath & ".tmp"
                writable.SaveTo(tempFile, DetectCompressionType(archive))

                archive.Dispose()
                File.Move(tempFile, archivePath, True)

                Console.WriteLine(vbLf & " File removed from archive.")
            Else
                Throw New NotSupportedException("This is a read-only archive.")
            End If
        End Using
    End Sub

#End Region
#Region "Helper functions"

    Function GetInput(prompt As String) As String
        Console.Write(prompt)
        Dim input As String = If(Console.ReadLine()?.Trim(), "")
        Console.WriteLine()
        Return input
    End Function

    Function FormatSize(bytes As Long) As String
        Dim sizes() As String = {"B", "KB", "MB", "GB"}
        Dim len As Double = bytes
        Dim order As Integer = 0

        While len >= 1024 AndAlso order < sizes.Length - 1
            order += 1
            len /= 1024
        End While

        Return $"{len:0.##} {sizes(order)}"
    End Function

    Function DetectCompressionType(archive As IArchive) As CompressionType
        Dim entry = archive.Entries.FirstOrDefault(Function(e) Not e.IsDirectory)
        Return If(entry?.CompressionType, CompressionType.Deflate)
    End Function

#End Region
End Module