# encoding: ascii
# api: powershell
# title: Read-DllExportFunctions
# description: Prints DLL exported functions list.
# version: 0.1
# type: function
# author: greg zakharov
# license: CC0
# function: Read-DllExportFunctions
# x-poshcode-id: 5262
# x-archived: 2014-07-04T02:47:11
# x-published: 2014-06-25T17:24:00
#
#
#requires -version 2.0
if (!(Test-Path alias:dllexp)) { Set-Alias dllexp Read-DllExportFunctions }

function Read-DllExportFunctions {
  <#
    .NOTES
        Author: greg zakharov
  #>
  param(
    [Parameter(Mandatory=$true, ValueFromPipeline=$true)]
    [ValidateScript({Test-Path $_})]
    [String]$FileName
  )
  
  begin {
    $cd = [AppDomain]::CurrentDomain
    $attr = 'AnsiClass, Class, Public, SequentialLayout, Sealed, BeforeFieldInit'
    
    if (!($cd.GetAssemblies() | ? {
      $_.FullName.Contains('ExpBrowse')
    })) {
      $type = (($cd.DefineDynamicAssembly(
        (New-Object Reflection.AssemblyName('ExpBrowse')), [Reflection.Emit.AssemblyBuilderAccess]::Run
      )).DefineDynamicModule('ExpBrowse', $false)).DefineType('IMAGE_EXPORT_DIRECTORY', $attr)
      [void]$type.DefineField('Characteristics',    [UInt32], 'Public')
      [void]$type.DefineField('TimeDateStamp',      [UInt32], 'Public')
      [void]$type.DefineField('MajorVersion',       [UInt16], 'Public')
      [void]$type.DefineField('MinorVersion',       [UInt16], 'Public')
      [void]$type.DefineField('Name',               [UInt32], 'Public')
      [void]$type.DefineField('Base',               [UInt32], 'Public')
      [void]$type.DefineField('NumberOfFunctions',  [UInt32], 'Public')
      [void]$type.DefineField('NumberOfNames',      [UInt32], 'Public')
      [void]$type.DefineField('AddressOfFunctions', [UInt32], 'Public')
      [void]$type.DefineField('AddressOfNames',     [UInt32], 'Public')
      [void]$type.DefineField('AddressOfOrdinals',  [UInt32], 'Public')
      $global:IMAGE_EXPORT_DIRECTORY = $type.CreateType()
    }
  }
  process {
    try {
      #read file
      $fs = [IO.File]::OpenRead($FileName)
      $buf = New-Object "Byte[]" $fs.Length
      [void]$fs.Read($buf, 0, $buf.Length)
      #validate that file is a PE
      $e_magic = -join [Char[]]$buf[0..1]
      $e_lfanew = 256 * $buf[0x3D] + $buf[0x3C]
      $pe_sign = -join [Char[]]$buf[$e_lfanew..($e_lfanew + 3)]
      
      if ($e_magic -ne 'MZ' -or $pe_sign -ne "PE`0`0") {
        throw (New-Object FormatException('Invalid file format.'))
      }
      #get PE bit and check what it is a DLL
      function Sync-Bytes([Int32]$offset) {
        return [BitConverter]::ToInt32($buf, ($e_lfanew + $offset))
      }
      
      $is32bit = ((Sync-Bytes 0x16) -band 0x0100) -eq 0x0100
      
      if (((Sync-Bytes 0x16) -band 0x2000) -eq 0x2000) {
        $ImageBase = if ($is32bit) {
          Sync-Bytes 0x34
        } else {
          [BitConverter]::ToInt64($buf, ($e_lfanew + 0x30))
        }
      }
      #fill structure
      $ied = [Activator]::CreateInstance($IMAGE_EXPORT_DIRECTORY)
      [Runtime.InteropServices.Marshal]::PtrToStructure(
        [IntPtr]($ImageBase + (Sync-Bytes 0x78)), $ied
      )
      0..($ied.NumberOfNames - 1) | % {
        [Runtime.InteropServices.Marshal]::PtrToStringAnsi(
          [IntPtr]($ImageBase + [Runtime.InteropServices.Marshal]::ReadInt32(
            ($ImageBase + $ied.AddressOfNames) + $_ * 4
          ))
        )
      }
    }
    catch { $e = [Boolean]$_ }
    finally {
      if ($fs -ne $null) { $fs.Close() }
      if ($e) { return }
    }
  }
  end {''}
}
