# encoding: ascii
# api: powershell
# title: Get-ZipContent
# description: Found in my personal scripts archive (author: greg zakharov)
# version: 0.1
# type: function
# author: Dana James
# license: CC0
# function: Get-ZipContent
# x-poshcode-id: 6136
# x-archived: 2016-03-19T08:09:13
# x-published: 2016-12-13T16:13:00
#
#
#requires -version 2
function Get-ZipContent {
  <#
    .SYNOPSIS
        Shows contents of the specified ZIP archive.
    .DESCRIPTION
        The function does not use any third party libraries that's why you
        shouldn't worry about dependencies.
    .PARAMETER Path
        The path to the ZIP archive.
    .EXAMPLE
        PS C:\> Get-ZipContent E:\*\SysinternalsSuite.zip
    .NOTES
        Notes: greg zakharov
  #>
  [CmdletBinding(DefaultParameterSetName='Path')]
  param(
    [Parameter(Mandatory=$true, ParameterSetName='Path', Position=0,
            ValueFromPipeline=$true, ValueFromPipelineByPropertyName=$true)]
    [ValidateScript({Test-Path $_})]
    [String]$Path,
    
    [Parameter(Mandatory=$true, ParameterSetName='LiteralPath', Position=0,
                                     ValueFromPipelineByPropertyName=$true)]
    [ValidateScript({Test-Path $_})]
    [String]$LiteralPath
  )
  
  begin {
    Set-StrictMode -Version 2.0
    
    function private:Read-Item([String]$Zip) {
      $Methods = @{Store = 0;Deflate = 8;BZIP2 = 12;LZMA = 14}
      
      function private:ConvertTo-DateTime([UInt16[]]$Stamp) {
        New-Object DateTime(
          ([Math]::Floor($Stamp[0] / [Math]::Pow(2, 9)) + 1980),
          ([Math]::Floor($Stamp[0] / [Math]::Pow(2, 5)) -band 0xF),
          ($Stamp[0] -band 0x1F),
          ([Math]::Floor($Stamp[1] / [Math]::Pow(2, 11))),
          ([Math]::Floor($Stamp[1] / [Math]::Pow(2, 5)) -band 0x3F),
          (($Stamp[1] -band 0x1F) * 2)
        )
      } # ConvertTo-DateTime
      
      try {
        $fs = [IO.File]::OpenRead($Zip)
        $br = New-Object IO.BinaryReader($fs)
        # locate central directory
        $fs.Position = $fs.Length - 22
        $fs.Position += 16
        $fs.Position = $br.ReadUInt32()
        # read structures
        while ($true) {
          if ($br.ReadUInt32() -ne 0x02014b50) {break}
          $fs.Position += 6 # skip next three fields
          # compression method and modification datetime
          $a, $b, $c = $br.ReadUInt16(), $br.ReadUInt16(), $br.ReadUInt16()
          # crc and sizes
          $d, $e, $f = $br.ReadUInt32(), $br.ReadUInt32(), $br.ReadUInt32()
          $g = $br.ReadUInt16() # file name length
          $h = $br.ReadUInt16() # extra field length
          $i = $br.ReadUInt16() # file comment length
          $fs.Position += 4
          $j = $br.ReadUInt32() # external file attributes
          $fs.Position += 4     # skip a field
          # print information of the current central directory file header
          $CentralDirectoryFileHeader = New-Object PSObject -Property @{
            Path = -join $br.ReadChars($g)
            Method = $Methods.Keys | Where-Object {$Methods.Item($_) -eq $a}
            Attributes = [IO.FileAttributes]$j
            Crc32 = "0x$($d.ToString('X8'))"
            Modified = ConvertTo-DateTime(@($c, $b))
            Packed = $e
            Size = $f
          }
          $CentralDirectoryFileHeader.PSObject.TypeNames.Insert(0, 'CentralDirectoryFileHeader')
          $CentralDirectoryFileHeader |
          Select-Object Path, Method, Attributes, Crc32, Modified, Packed, Size
          $fs.Position += $h + $i
        }
      }
      catch { Write-Debug $_.Exception }
      finally {
        if ($br -ne $null) { $br.Close() }
        if ($fs -ne $null) { $fs.Close() }
      }
    } # Read-Item
  }
  process {
    Read-Item $(switch ($PSCmdlet.ParameterSetName) {
      'Path'        { Resolve-Path $Path }
      'LiteralPath' { $LiteralPath }
    })
  }
  end {}
}
