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