65

I have a .tar.gz file that I need to extract. I've handled the gunzip bit with the GzipStream object from System.IO.Compression, but I couldn't find anything for dealing with tarballs in that namespace. Is there a way to deal with .tar files natively in Powershell? Note that it's only important that I be able to call any such function/method/object construction/system binary from a Powershell script; it doesn't need to actually be written in powershell. (If it matters I'm using 64-bit windows 10)

P.S. please don't say "use 7zip"; that's not native

bfieck
  • 989
  • 1
  • 7
  • 17
  • Is there a reason for this necessity? I only ask because I couldn't find an answer to this too. – kayleeFrye_onDeck Aug 04 '16 at 20:33
  • Maybe, but I don't think so, and I wouldn't expect it. Gzip is used by HTTP compression, at least ASP.Net, Invoke-WebRequest, IE/Edge would need to support it, but what in the Windows ecosystem is based on 'tar'? – TessellatingHeckler Aug 05 '16 at 00:47
  • When you say native do you mean "pure" PowerShell or would you be happy to load a binary PowerShell module with a dot net class? – Glen Buktenica Aug 05 '16 at 03:02
  • 1
    You'll either have to write a tar parser in PowerShell or import a third-party library like SharpCompress or SharpZipLib – Mathias R. Jessen Aug 05 '16 at 10:12
  • @GlenBuktenica The only thing I'm looking for is using utilities that already exist on my system, and systems similar to mine. I'll load any class, binary, assembly, what have you. It just needs to be _callable_ from the powershell script. In fact, I think I could state my question a little bit more clearly with regard to that. – bfieck Aug 05 '16 at 15:35
  • @MathiasR.Jessen I'm not opposed to writing my own, if that's what it takes. If the answer to my question really is just "No, there's no pre-existing structure to handle tarballs that ships with Windows" I'd be happy to give it the big 'ol green check mark. I don't expect anyone to write a tar parser for me, that's a separate question altogether (and not a very good one either), it's enough to just know that it can't be done. That said, it's a lot harder to definitively say "it can't be done" than "it can be done" so I may be waiting a long time for an answer :P – bfieck Aug 05 '16 at 15:42
  • 1
    @kayleeFrye_onDeck I was trying to install gnu Make and gnu FindUtils on Windows 10 computers, with the intention of replacing slow gulp tasks with fast make recipes (Windows `FIND` is unusable to someone used to the gnu version, hence findutils). The source for these is only available as tarballs or zips on sourceforge, but sourceforge doesn't support static file hosting, so I couldn't download from there without advanced tools like `cURL` (unavailable on vanilla installs of Windows). I hope that helps with whatever you needed this for. – bfieck Aug 15 '16 at 20:15
  • 1
    As of January 2018, `tar` and `curl` are included in Windows. Originally this was only available to Windows Insiders but I believe it is available for all Windows 10 users now (https://devblogs.microsoft.com/commandline/tar-and-curl-come-to-windows/). It works how you would expect, but I posted an answer just in case. – Broper Apr 24 '19 at 03:41

8 Answers8

74

I believe tar has been added as a native function in Windows 10 since the posting of this.

From the command prompt or PowerShell in Windows 10 I can run

tar -xvzf .\whatever.tar.gz

Note that the .\ was added after auto-completing by the use of tab in PowerShell, but I think it should work without that.

There may be some underlying differences between this function and its Unix implementation (since it is on Windows after all), but it worked for me.

Broper
  • 2,000
  • 1
  • 14
  • 15
  • 1
    This seems to be the best and simplest solution for me! Thanks – SPYBUG96 Aug 20 '20 at 18:31
  • 4
    This works very well! I had multiple tar.gz files in a single folder and I used the command like this: `gci *.tar.gz | ForEach-Object {tar -xvzf $_}` to uncompress all of them. It creates a separate folder for each tar file, too. This was on a Windows Server 2019 Standard running Powershell v.5.1, BTW. – senpai Jan 22 '21 at 22:27
  • [Tar and Curl Come To Windows!](https://techcommunity.microsoft.com/t5/containers/tar-and-curl-come-to-windows/ba-p/382409) – derekbaker783 May 02 '21 at 13:19
  • Its a BSD tar from libarchive.org. I think its as good as any UNIX tar. – caoanan Jul 19 '21 at 03:10
27

Update in 2019:

As pointed out in other answers, BSD tar was added to Windows 10 as a built-in command in 2018. This answer is still applicable if you need to support earlier versions of Windows.


I'm fairly sure that Windows 10 did not have support for extracting tar files by default prior to 2018.

1. The quick easy solution for (early versions of) Windows 10

If you can expect the script to have access to an active Internet connection, then you can simply get 7Zip support using the built-in package manager. This solution:

  • Is entirely automated via script (does not require user interaction)
  • Does not require administrator privileges
  • Only installs the 7Zip support module if it isn't already available

Here it is:

function Expand-Tar($tarFile, $dest) {

    if (-not (Get-Command Expand-7Zip -ErrorAction Ignore)) {
        Install-Package -Scope CurrentUser -Force 7Zip4PowerShell > $null
    }

    Expand-7Zip $tarFile $dest
}

To use it:

Expand-Tar archive.tar dest

It does the job but it makes a persistent change to the client system. There is a better, but slightly more involved, solution:

2. The better solution

The better solution is to bundle 7Zip4PowerShell with your script. This solution:

  • Is entirely automated via script (does not require user interaction)
  • Does not require administrative privileges
  • Does not install any software on the client system
  • Does not require internet connectivity
  • Should work on earlier versions of Windows

a. Download a copy of the 7Zip4PowerShell package

It's distributed under the LGPL-2.1 licence and so it's OK to bundle the package with your product. You should add a note to your documentation that it uses the package and provide a link since you are required to provide access to the source code for GNU licenced products.

Save-Module -Name 7Zip4Powershell -Path .

This will download it to the current directory. It's easiest to do this on Windows 10 but there are instructions on how to add PowerShell Gallery to your system on earlier versions of Windows from the link above.

b. Import the module when you need to extract the tar file

I've made a simple function just to demonstrate how you could do this based on the first solution:

function Expand-Tar($tarFile, $dest) {

    $pathToModule = ".\7Zip4Powershell\1.9.0\7Zip4PowerShell.psd1"

    if (-not (Get-Command Expand-7Zip -ErrorAction Ignore)) {
        Import-Module $pathToModule
    }

    Expand-7Zip $tarFile $dest
}

You'll need to adjust $pathToModule to where the bundled module is actually stored when your script runs, but you should get the idea. I've written this example such that you can simply paste in both of the code blocks above into a PowerShell window and get a working Expand-Tar cmdlet:

Expand-Tar archive.tar dest
Don Cruickshank
  • 5,641
  • 6
  • 48
  • 48
  • 1
    Nice helper! One thing to note, version 1.9.0 of 7Zip4Powershell is out. You can either change the `$pathToModule` or add `-RequiredVersion '1.8.0'` to the `Save-Module` command. – Aidan Ryan Feb 12 '19 at 19:41
  • @AidanRyan Thanks - I've updated it to 1.9.0 as I can't see an easy workaround and it feels wrong to suggest earlier versions. – Don Cruickshank Feb 12 '19 at 20:48
  • 2
    I believe an implementation for both `tar` and `curl` were added to Windows 10 around the beginning of 2018 (https://devblogs.microsoft.com/commandline/tar-and-curl-come-to-windows/). It works similarly to the Unix command, but I posted an answer as well. – Broper Apr 24 '19 at 03:39
  • @Broper Thanks - I've added a note to my answer about that. – Don Cruickshank Apr 24 '19 at 08:52
  • 1
    Specifically build 17063 added it – Slate Jan 02 '20 at 12:30
6

Possible workaround:

I have Windows 10 and I wanted to run curl+tar within Powershell but it lacked support. Luckily, I had Ubuntu bash on Windows 10 which comes with curl and tar preinstalled and ran it within that environment, then after download and extracting, I switched back to Powershell to continue whatever I was doing.

Or as @Don Cruickshank mentioned in comment below you can install 7zip support from within PowerShell directly with Install-Package 7Zip4Powershell. The Expand-7zip cmdlet will extract multiple archive formats, including tar.

These workarounds don't solve your particular problem, but for these that are stuck with Windows, might be useful.

Kunok
  • 8,089
  • 8
  • 48
  • 89
  • 2
    In Windows 10, you can install 7zip support from within PowerShell directly with `Install-Package 7Zip4Powershell`. The `Expand-7zip` cmdlet will extract multiple archive formats, including tar. – Don Cruickshank Jul 30 '17 at 22:41
  • @DonCruickshank Thanks for this suggestion. – Kunok Jul 30 '17 at 22:43
4

Windows 10 since 2017 has tar build in so may run it as usual exe from powershell liketar -xkf $archivePath -C $outDir, or better from pwsh.

Dzmitry Lahoda
  • 939
  • 1
  • 13
  • 34
2

It took me some time but here is the solution I've made. Basically TAR is a very simple format but even then it takes some lines of code to deal with it.

Function ConvertTo-ByteString
{
    Param(
    [Parameter(Mandatory = $True, Position = 0, ValueFromPipeline = $True )][byte[]]$Buffer
    )

    # Note: Codepage 28591 returns a 1-to-1 char to byte mapping
    $Encoding = [System.Text.Encoding]::GetEncoding(28591)
    #$BinaryText = [System.Text.Encoding]::Convert('ascii', '28591', $Buffer)
    $BinaryText = [System.Text.Encoding]::ASCII.GetString($Buffer)
    $BinaryText = $BinaryText.Trim(0)
    $BinaryText = $BinaryText.Trim(32)
    $BinaryText = $BinaryText.Trim(0)
    $BinaryText = $BinaryText.Trim(32)
    return $BinaryText
}

Function New-USTARHeaderObject
{
    #Write-Host('[New-USTARHeaderObject]','Creating new header object') -ForegroundColor Cyan
    $Header = New-Object -Type PSObject
    $Header | Add-Member -MemberType NoteProperty -Name name -Value $null
    $Header | Add-Member -MemberType NoteProperty -Name mode -Value $null
    $Header | Add-Member -MemberType NoteProperty -Name uid -Value $null
    $Header | Add-Member -MemberType NoteProperty -Name gid -Value $null
    $Header | Add-Member -MemberType NoteProperty -Name size -Value $null
    $Header | Add-Member -MemberType NoteProperty -Name mtime -Value $null
    $Header | Add-Member -MemberType NoteProperty -Name cksum -Value $null
    $Header | Add-Member -MemberType NoteProperty -Name typeflag -Value $null
    $Header | Add-Member -MemberType NoteProperty -Name linkname -Value $null
    $Header | Add-Member -MemberType NoteProperty -Name magic -Value $null
    $Header | Add-Member -MemberType NoteProperty -Name version -Value $null
    $Header | Add-Member -MemberType NoteProperty -Name uname -Value $null
    $Header | Add-Member -MemberType NoteProperty -Name gname -Value $null
    $Header | Add-Member -MemberType NoteProperty -Name devmajor -Value $null
    $Header | Add-Member -MemberType NoteProperty -Name devminor -Value $null
    $Header | Add-Member -MemberType NoteProperty -Name prefix -Value $null

    return $Header
   
}

Function Fill-USTARHeaderObject
{
    Param(
    [Parameter(Mandatory=$True)][PSObject]$Header,
    [Parameter(Mandatory=$True)][byte[]]$Buffer
    )
    
    #Write-Host('[Fill-USTARHeaderObject]','Filling header object with bytes') -ForegroundColor Cyan
    $Header.name = [string](ConvertTo-ByteString $Buffer[0..99])
    $Header.mode = ConvertTo-ByteString $Buffer[100..107]
    $Header.uid = ConvertTo-ByteString $Buffer[108..115]
    $Header.gid = ConvertTo-ByteString $Buffer[116..123]
    $Header.size = ConvertTo-ByteString $Buffer[124..135]
    $Header.mtime = ConvertTo-ByteString $Buffer[136..147]
    $Header.cksum = ConvertTo-ByteString $Buffer[148..155]
    $Header.typeflag = ConvertTo-ByteString $Buffer[156]
    $Header.linkname = ConvertTo-ByteString $Buffer[157..256]
    $Header.magic = ConvertTo-ByteString $Buffer[257..262]
    $Header.version = ConvertTo-ByteString $Buffer[263..264]
    $Header.uname = ConvertTo-ByteString $Buffer[265..296]
    $Header.gname = ConvertTo-ByteString $Buffer[297..328]
    $Header.devmajor = ConvertTo-ByteString $Buffer[329..336]
    $Header.devminor = ConvertTo-ByteString $Buffer[337..344]
    $Header.prefix = ConvertTo-ByteString $Buffer[345..499]
}


Function Check-IsUSTARHeaderObject
{
    Param(
    [Parameter(Mandatory=$True, Position = 0, ValueFromPipeline = $True)][PSObject]$Header
    )
    $Regex_Numeric = [regex]"^\d+$"
    #Write-Host('[Check-IsUSTARHeaderObject]','Checking if object is actual header') -ForegroundColor Cyan

    #Write-Host("[Mode    ]",$Header.mode,($Regex_Numeric.Matches($Header.mode).Success)) -ForegroundColor Magenta
    #Write-Host("[Size    ]",$Header.size,($Regex_Numeric.Matches($Header.size).Success)) -ForegroundColor Magenta
    #Write-Host("[MTime   ]",$Header.mtime,($Regex_Numeric.Matches($Header.mtime).Success)) -ForegroundColor Magenta
    #Write-Host("[CKSum   ]",$Header.cksum,($Regex_Numeric.Matches($Header.cksum).Success)) -ForegroundColor Magenta
    #Write-Host("[TypeFlag]",$Header.typeflag,($Regex_Numeric.Matches($Header.typeflag).Success)) -ForegroundColor Magenta
    #Write-Host("[Magic   ]",$Header.magic,($Header.magic -eq 'ustar')) -ForegroundColor Magenta

    if (
        ($Regex_Numeric.Matches($Header.mode).Success) -and
        ($Regex_Numeric.Matches($Header.size).Success) -and
        ($Regex_Numeric.Matches($Header.mtime).Success) -and
        ($Regex_Numeric.Matches($Header.cksum).Success) -and
        ($Regex_Numeric.Matches($Header.typeflag).Success) -and
        ($Header.magic -eq 'ustar')
    )
    {
        #Write-Host('[Check-IsUSTARHeaderObject]','TRUE') -ForegroundColor DarkCyan
        return $true
    } else {
        #Write-Host('[Check-IsUSTARHeaderObject]','FALSE') -ForegroundColor DarkCyan
        return $false
    }
}


Function UnTar-File
{
    Param(
    [Parameter(Mandatory=$True)][String]$Source,
    [Parameter(Mandatory=$False)][String]$Destination
    )
   
    #$Source = 'D:\temp\th-dfbase-blacklist.tar'
    $SourceBaseName = (Get-Item $Source).BaseName
    Write-Host('[UnTar-File]','Setting Source',$Source) -ForegroundColor Cyan

    #if destination parameter not given
    if ($Destination -eq "")
    {
        $SourcePath = Split-Path $Source
        $OutFolder = Join-Path -Path $SourcePath -ChildPath $SourceBaseName
    }
    else
    {
        $OutFolder = Join-Path -Path $Destination -ChildPath $SourceBaseName
    }

    Write-Host('[UnTar-File]','Destination Folder',$OutFolder) -ForegroundColor DarkCyan

    $FileStream = New-Object System.IO.FileStream $Source, ([IO.FileMode]::Open), ([IO.FileAccess]::Read), ([IO.FileShare]::Read)

    $BufferSize = 512
    $Buffer = New-Object byte[]($BufferSize)

    Write-Host('[UnTar-File]','Reading Source by', $BufferSize,'bytes') -ForegroundColor Cyan
    while($true)
    {
    
        $read = $FileStream.Read($Buffer, 0, $BufferSize)
        #Write-Host($read) -ForegroundColor Cyan

        if ($read -le 0){break}

        #Try Header
        $Header = New-USTARHeaderObject
        Fill-USTARHeaderObject -Header $Header -Buffer $Buffer

        #if header section
        if (Check-IsUSTARHeaderObject($Header))
        {
            $ItemPath = [IO.Path]::Combine($Header.prefix, $Header.name)
            $ItemPath = $ItemPath.Replace('/','\')

            #if folder type
            if ($Header.typeflag -eq '5')
            {
                $FolderPath = Join-Path -Path $OutFolder -ChildPath $ItemPath
                Write-Host('[UnTar-File]','Creating Folder',$FolderPath) -ForegroundColor Yellow
                New-Item -Path $FolderPath -ItemType Directory -Force | Out-Null
            }
            #if file type
            else
            {
                $FilePath = Join-Path -Path $OutFolder -ChildPath $ItemPath
                Write-Host('[UnTar-File]','Creating File',$FilePath) -ForegroundColor DarkYellow
                if ($OutputFile -ne $null)
                {
                    $OutputFile.Close()
                }
                $OutputFileInfo = New-Object PSObject
                $OutputFileInfo | Add-Member -MemberType NoteProperty -Name name -Value $Header.name
                $OutputFileInfo | Add-Member -MemberType NoteProperty -Name path -Value (Split-Path -Path $FilePath)
                $OutputFileInfo | Add-Member -MemberType NoteProperty -Name size -Value ([Convert]::ToInt32($Header.size, 8))
                $OutputFileInfo | Add-Member -MemberType NoteProperty -Name left2write -Value ([Convert]::ToInt32($Header.size, 8))
                $OutputFileInfo | Add-Member -MemberType NoteProperty -Name mtime -Value ([Convert]::ToInt32($Header.mtime, 8))
                $OutputFile = New-Object System.IO.FileStream $FilePath, ([IO.FileMode]::Create), ([IO.FileAccess]::Write), ([IO.FileShare]::None)
            }
        }
        #if data section
        else
        {
            if ($OutputFileInfo.left2write -ge $BufferSize)
            {
                $WriteSize = $read
            } else {
                $WriteSize = $OutputFileInfo.left2write
            }
            $OutputFile.Write($Buffer, 0, $WriteSize)
            $OutputFileInfo.left2write = ($OutputFileInfo.left2write - $WriteSize)
        }
    }
    $FileStream.Close()
    if ($OutputFile -ne $null)
    {
        $OutputFile.Close()
    }
}


UnTar-File -Source $Tar
user2838376
  • 149
  • 2
  • 11
1

So it's been eleven days since I asked this and the general consensus is: "No, there are no native tools in a vanilla window install that can handle tar extraction 'for you'".

This answer comes from Matthias R. Jensen and TessellatingHeckler, who both declined to answer outside of comments (I suspect due to not wanting to say "no" without an intimate knowledge of the entire Windows system architecture, which is fair).

There are certainly scripts and classes and programs you can install, but nothing "native".

Community
  • 1
  • 1
bfieck
  • 989
  • 1
  • 7
  • 17
1

This snippet has been working for me, I don't think I have anything installed that it is using. I still have to figure out how to extract the tar that this pull out.

Function DeGZip-File{
    Param(
        $infile
        )
    $outFile = $infile.Substring(0, $infile.LastIndexOfAny('.'))
    $input = New-Object System.IO.FileStream $inFile, ([IO.FileMode]::Open), ([IO.FileAccess]::Read), ([IO.FileShare]::Read)
    $output = New-Object System.IO.FileStream $outFile, ([IO.FileMode]::Create), ([IO.FileAccess]::Write), ([IO.FileShare]::None)
    $gzipStream = New-Object System.IO.Compression.GzipStream $input, ([IO.Compression.CompressionMode]::Decompress)

    $buffer = New-Object byte[](1024)
    while($true){
        $read = $gzipstream.Read($buffer, 0, 1024)
        if ($read -le 0){break}
        $output.Write($buffer, 0, $read)
        }

    $gzipStream.Close()
    $output.Close()
    $input.Close()
}

$infile='D:\[Directory]\[File].tar.gz'

DeGZip-File $infile 
user2197169
  • 147
  • 1
  • 5
1

This is the fragment for a PowerShell script:

try
{
    Add-Type -AssemblyName "ICSharpCode.SharpZLib"

    $file = [IO.File]::OpenRead($gzArchiveName)
    $inStream=New-Object -TypeName ICSharpCode.SharpZipLib.GZip.GZipInputStream $file
    $tarIn = New-Object -TypeName ICSharpCode.SharpZipLib.Tar.TarInputStream $inStream
    $archive = [ICSharpCode.SharpZipLib.Tar.TarArchive]::CreateInputTarArchive($tarIn)
    $archive.ExtractContents($WORKDIR)
}
finally
{
    $archive.Close
}

which is equivalent to the code of TarTool.exe utility

  • `Add-Type -AssemblyName "ICSharpCode.SharpZLib"` Fails right away. `Add-Type : Cannot add type. The assembly 'ICSharpCode.SharpZLib' could not be found.` – Andy Jul 08 '21 at 14:55