# encoding: ascii
# api: powershell
# title: Reading RAR
# description: The main goal of this function to not use additional libraries.
# version: 0.1
# type: function
# author: greg zakharov
# license: CC0
# function: Get-RarContent
# x-poshcode-id: 6711
# x-archived: 2017-01-30T05:37:28
# x-published: 2017-01-24T11:43:00
#
#
function Get-RarContent {
  <#
    .SYNOPSIS
        Gets content of a compressed (RAR) file.
    .DESCRIPTION
        The main goal of this function to not use additional binary
        files such as WinRAR.exe and etc.
    .NOTES
        Author: greg zakharov
    .LINKS
        https://github.com/gregzakh/alt-ps/blob/master/tools/Get-RarContent.ps1
  #>
  param(
    [Parameter(Mandatory=$true)]
    [ValidateNotNullOrEmpty()]
    [ValidateScript({Test-Path $_})]
    [String]$Path
  )

  begin {
    # rar signature
    [Byte[]]$rar = 0x52, 0x61, 0x72, 0x21, 0x1A, 0x07, 0x00
  }
  process {
    try {
      $fs = [IO.File]::OpenRead((Convert-Path $Path))
      $br = New-Object IO.BinaryReader($fs)

      if (($cmp = Compare-Object ($br.ReadBytes(7)) $rar -PassThru)) {
        throw New-Object IO.IOException(
          'Specified file is nat a RAR archive, try another.'
        )
      }
      # MAIN_HEAD
      if (($mhd = $br.ReadBytes(7))[2] -ne 0x73) {
        throw New-Object IO.IOException(
          'Required structure MAIN_HEAD has not been found.'
        )
      }
      # move to the first entry
      [void]$fs.Seek((
        [BitConverter]::ToUInt16($mhd[5..6], 0) - 7
      ), [IO.SeekOrigin]::Current)
      # walk through all available items
      $content = while ($true) {
        $seg = $br.ReadBytes(7) # beginning of a block
        if (($bsz = [BitConverter]::ToUInt16($seg[5..6], 0)) -le 7) {
          break # nothing to read
        }

        $seg += $br.ReadBytes($bsz - 7) # whole block
        # check that current entry is a file system object
        if ($seg[2] -eq 0x74) {
          [void]$fs.Seek((
            $cmp = [BitConverter]::ToUInt32($seg[7..10], 0) # compressed
          ), [IO.SeekOrigin]::Current)

          if ((
            $atr = [BitConverter]::ToUInt32($seg[28..31], 0)
          ) -band 0x10 -or $atr -band 0x4000) {
            continue # do you really need to see paths of folders?
          }

          New-Object PSObject -Property @{
            Name = -join [Char[]]$seg[32..(
              32 + [BitConverter]::ToUInt16($seg[26..27], 0
            ) - 1)]
            Cmpd = $cmp
            Size = [BitConverter]::ToUInt32($seg[11..14], 0)
            Attr = -join (([IO.FileAttributes]$atr).ToString().Split(
              ', ', [StringSplitOptions]::RemoveEmptyEntries
            ) | ForEach-Object { $_[0] })
            #Crc32 = '0x{0:X}' -f ([BitConverter]::ToUInt32($seg[16..19], 0))
          }
        }
        else {
          if ([BitConverter]::ToUInt16($seg[3..4], 0) -band 0x8000) {
            [void]$fs.Seek([BitConverter]::ToUInt32(
              $seg[7..10], 0), [IO.SeekOrigin]::Current
            )
          }
        }
      } # while
    }
    catch { Write-Warning $_ }
    finally {
      if ($br) { $br.Close() }
      if ($fs) { $fs.Dispose() }
    }
  }
  end {
    if ($content) {
      $content | Select-Object Attr, Size, Cmpd, Name |
      Format-Table -AutoSize
    }
  }
}
