How to Create, Extract and Update Tar GZIP File in Windows

Recently, our team needs to regularly update Tar GZIP files that are built for Linux on Windows.  GZIP is a file format for file compression and decompression. Unlike ZIP, GZIP is used to compress just one single file. Usually, we have to assemble files into a single tar archive, and then compress that archive with gzip (.tar.gz or .tgz). I spent some time looking for the workaround of packaging TAR GZIP files on Windows.

Linux Commands

Windows 10 now supports installing Linux distributions as subsystems. To create tar.gz packages, it is convenient to use the Linux commands in command line tools.

Examples

Create a .tar.gz archive:

tar -czvf dwt.tar.gz dwt/

Extract an archive to a directory

tar -xzvf dwt.tar.gz -C tmp/

7zip Commands

7zip is competent for compressing and decompressing .tar.gz files in Windows.

Create .tar.gz:

7z a -ttar -so dwt.tar dwt/ | 7z a -si dwt.tar.gz

Extract .tar.gz:

7z x dwt.tar.gz -so | 7z x -si -ttar

Update tar.gz:

7z x dwt.tar.gz && 7z u dwt.tar dwt && del dwt.tar.gz && 7z a dwt.tar.gz dwt.tar && del dwt.tar

Create, Extract and Update TAR GZIP File in C#

To programmatically operating .tar.gz files, we can use SharpZipLib. The tutorial GZip and Tar Samples is also very helpful.

Create, Extract and Update TAR GZIP File in C#

Create a .tar.gz file

private void CreateTarGZ(string tgzFilename, string sourceDirectory)
        {
            Stream outStream = File.Create(tgzFilename);
            Stream gzoStream = new GZipOutputStream(outStream);
            TarArchive tarArchive = TarArchive.CreateOutputTarArchive(gzoStream);

            // Note that the RootPath is currently case sensitive and must be forward slashes e.g. "c:/temp"
            // and must not end with a slash, otherwise cuts off first char of filename
            // This is scheduled for fix in next release
            tarArchive.RootPath = sourceDirectory.Replace('\\', '/');
            if (tarArchive.RootPath.EndsWith("/"))
                tarArchive.RootPath = tarArchive.RootPath.Remove(tarArchive.RootPath.Length - 1);

            AddDirectoryFilesToTar(tarArchive, sourceDirectory, true, true);

            tarArchive.Close();
        }

private void AddDirectoryFilesToTar(TarArchive tarArchive, string sourceDirectory, bool recurse, bool isRoot)
        {

            // Optionally, write an entry for the directory itself.
            // Specify false for recursion here if we will add the directory's files individually.
            //
            TarEntry tarEntry;

            if (!isRoot)
            {
                tarEntry = TarEntry.CreateEntryFromFile(sourceDirectory);
                tarArchive.WriteEntry(tarEntry, false);
            }

            // Write each file to the tar.
            //
            string[] filenames = Directory.GetFiles(sourceDirectory);
            foreach (string filename in filenames)
            {
                tarEntry = TarEntry.CreateEntryFromFile(filename);
                Console.WriteLine(tarEntry.Name);
                tarArchive.WriteEntry(tarEntry, true);
            }

            if (recurse)
            {
                string[] directories = Directory.GetDirectories(sourceDirectory);
                foreach (string directory in directories)
                    AddDirectoryFilesToTar(tarArchive, directory, recurse, false);
            }
        }

Extract a .tar.gz file

public void ExtractTGZ(String gzArchiveName, String destFolder)
        {

            Stream inStream = File.OpenRead(gzArchiveName);
            Stream gzipStream = new GZipInputStream(inStream);

            TarArchive tarArchive = TarArchive.CreateInputTarArchive(gzipStream);
            tarArchive.ExtractContents(destFolder);
            tarArchive.Close();

            gzipStream.Close();
            inStream.Close();
        }

Update a .tar.gz file

Updating a .tar.gz file is a little complicated because we have to decompress .tar.gz to .tar first, and then update the .tar and compress it to .tar.gz again.

Extract .tar.gz to .tar:

public string ExtractGZipFile(string gzipFileName, string targetDir)
        {

            // Use a 4K buffer. Any larger is a waste.    
            byte[] dataBuffer = new byte[4096];

            using (System.IO.Stream fs = new FileStream(gzipFileName, FileMode.Open, FileAccess.Read))
            {
                using (GZipInputStream gzipStream = new GZipInputStream(fs))
                {

                    // Change this to your needs
                    string fnOut = Path.Combine(targetDir, Path.GetFileNameWithoutExtension(gzipFileName));

                    using (FileStream fsOut = File.Create(fnOut))
                    {
                        StreamUtils.Copy(gzipStream, fsOut, dataBuffer);
                    }

                    return fnOut;
                }
            }
        }

Update .tar:

public void UpdateTar(string tarFileName, string targetFile, bool asciiTranslate)
        {
            using (FileStream fsIn = new FileStream(tarFileName, FileMode.Open, FileAccess.Read))
            {
                string tmpTar = Path.Combine(Path.GetDirectoryName(tarFileName), "tmp.tar");
                using (FileStream fsOut = new FileStream(tmpTar, FileMode.OpenOrCreate, FileAccess.Write))
                {
                    TarOutputStream tarOutputStream = new TarOutputStream(fsOut);
                    TarInputStream tarIn = new TarInputStream(fsIn);
                    TarEntry tarEntry;
                    while ((tarEntry = tarIn.GetNextEntry()) != null)
                    {

                        if (tarEntry.IsDirectory)
                        {
                            continue;
                        }
                        // Converts the unix forward slashes in the filenames to windows backslashes
                        //
                        string name = tarEntry.Name.Replace('/', Path.DirectorySeparatorChar);
                        string sourceFileName = Path.GetFileName(targetFile);
                        string targetFileName = Path.GetFileName(tarEntry.Name);

                        if (sourceFileName.Equals(targetFileName))
                        {
                            using (Stream inputStream = File.OpenRead(targetFile))
                            {

                                long fileSize = inputStream.Length;
                                TarEntry entry = TarEntry.CreateTarEntry(tarEntry.Name);

                                // Must set size, otherwise TarOutputStream will fail when output exceeds.
                                entry.Size = fileSize;

                                // Add the entry to the tar stream, before writing the data.
                                tarOutputStream.PutNextEntry(entry);

                                // this is copied from TarArchive.WriteEntryCore
                                byte[] localBuffer = new byte[32 * 1024];
                                while (true)
                                {
                                    int numRead = inputStream.Read(localBuffer, 0, localBuffer.Length);
                                    if (numRead <= 0)
                                    {
                                        break;
                                    }
                                    tarOutputStream.Write(localBuffer, 0, numRead);
                                }
                            }
                            tarOutputStream.CloseEntry();
                        }
                        else
                        {
                            tarOutputStream.PutNextEntry(tarEntry);

                            if (asciiTranslate)
                            {
                                CopyWithAsciiTranslate(tarIn, tarOutputStream);
                            }
                            else
                            {
                                tarIn.CopyEntryContents(tarOutputStream);
                            }

                            tarOutputStream.CloseEntry();
                        }
                    }
                    tarIn.Close();
                    tarOutputStream.Close();
                }

                File.Delete(tarFileName);
                File.Move(tmpTar, tarFileName);
            }
        }

Update .tar.gz:

private void UpdateTarGZ(string tgzFilename, string tarFileName)
        {
            Stream gzoStream = new GZipOutputStream(File.Create(tgzFilename));

            using (FileStream source = File.Open(tarFileName,
            FileMode.Open))
            {

                byte[] localBuffer = new byte[32 * 1024];
                while (true)
                {
                    int numRead = source.Read(localBuffer, 0, localBuffer.Length);
                    if (numRead <= 0)
                    {
                        break;
                    }
                    gzoStream.Write(localBuffer, 0, numRead);
                }
            }

            gzoStream.Close();

            File.Delete(tarFileName);
        }

Build the app:

dotnet publish -c Release -r win10-x64

Run the app using ‘dotne run’ or ‘targzip’:

tar gzip csharp

Source Code

https://github.com/yushulx/dotnet-tar-gzip