# encoding: ascii
# api: powershell
# title: Get-MacAddress
# description: Found at https://github.com/gregzakh/alt-ps/blob/master/Get-MacAddress.ps1
# version: 0.1
# type: function
# author: terran
# license: CC0
# function: Get-MacAddress
# x-poshcode-id: 6458
# x-archived: 2016-09-07T05:22:31
# x-published: 2016-07-27T13:30:00
#
#
function Get-MacAddress {
  <#
    .SYNOPSIS
        Gets MAC address.
    .NOTES
        Alternative ways to do same.
        
        Example 1:
        $asm = Add-Type -MemberDefinition @'
          [DllImport("rpcrt4.dll")]
          public static extern Int32 UuidCreateSequential(
              out Guid guid
          );
        '@ -Name Uuid -NameSpace MacAddress -PassThru
        $guid = New-Object Guid
        
        if (($res = $asm::UuidCreateSequential([ref]$guid)) -ne 0) {
          (New-Object ComponentModel.Win32Exception($res)).Message
          break
        }
        [Regex]::Replace(
          $guid.Guid.Split('-')[-1], '.{2}', '$0-'
        ).TrimEnd('-')
        
        Example 2:
        Get-WmiObject Win32_NetworkAdapter | Where-Object {
          $_.MacAddress
        } | ForEach-Object {
          New-Object PSObject -Property @{
            Description = $_.Description
            Service     = $_.ServiceName
            MACAddress  = $_.MACAddress
          }
        } | Select-Object Description, Service, MACAddress
        
        Example 3:
        Get-WmiObject Win32_NetworkAdapterConfiguration | Where-Object {
          $_.MacAddress
        } | ForEach-Object {
          New-Object PSObject -Property @{
            Description = $_.Description
            Id          = $_.SettingID
            MACAddress  = $_.MACAddress
          }
        } | Select-Object Description, Id, MACAddress
        
        Example 4:
        $mac, $nic = (getmac /fo csv | Where-Object {
          ![String]::IsNullOrEmpty($_) -and $_ -match '\w{2}\-'
        }).Split(',') | ForEach-Object {$_.Trim('"')}
        New-Object PSObject -Property @{
          Interface  = $nic.Substring(($$ = $nic.IndexOf('{')), $nic.Length - $$)
          MACAddress = $mac
        }
        
        Example 5:
        $key = 'HKLM:\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters\Interfaces\*'
        Get-ItemProperty $key | Where-Object {
          $_.DhcpIpAddress -and $_.DhcpIpAddress -ne '0.0.0.0'
        } | Select-Object DhcpIpAddress | ForEach-Object {
          New-Object PSObject -Property @{
            IPAddress  = $_.DhcpIpAddress
            MACAddress = ([Regex]'(\w{2}\-){5}\w{2}').Match((
              nbtstat -a $_.DhcpIpAddress
            )).Value
          }
        }
        
        Example 6:
        ([Regex]'(\w{2}\-){5}\w{2}').Match((
            ipconfig /all
        )).Value
        
        Example 7:
        ([Regex]'(\w{2}\s){6}').Match((
            route print
        )).Value.Trim().Replace([Char]32, '-')
  #>
  
  #there is no access denied error by path Global\.net clr networking
  #in CLR v4 for limited user, so...
  if (($clr = $PSVersionTable.CLRVersion.Major) -ge 4) {
    [Net.NetworkInformation.NetworkInterface]::GetAllNetworkInterfaces() |
    Where-Object {
      $_.OperationalStatus -eq [Net.NetworkInformation.OperationalStatus]::Up
    } | ForEach-Object {
      if (![String]::IsNullOrEmpty((
        $$ = $_.GetPhysicalAddress().ToString()
      ))) {
        New-Object PSObject -Property @{
          Id         = $_.Id
          MACAddress = [Regex]::Replace($$, '.{2}', '$0-').TrimEnd('-')
        }
      }
    } | Select-Object Description, Id, MACAddress | Format-List
  }
  elseif ($clr -eq 2) {
    @(
      [Runtime.InteropServices.CallingConvention],
      [Runtime.InteropServices.Marshal],
      [Reflection.BindingFlags],
      [Reflection.Emit.OpCodes]
    ) | ForEach-Object {
      $keys = ($ta = [PSObject].Assembly.GetType(
        'System.Management.Automation.TypeAccelerators'
      ))::Get.Keys
      $collect = @()
    }{
      if ($keys -notcontains $_.Name) {
        $ta::Add($_.Name, $_)
      }
      $collect += $_.Name
    }
    
    function Get-LastError {
      param(
        [Int32]$ErrorCode = [Marshal]::GetLastWin32Error()
      )
      
      [PSObject].Assembly.GetType(
        'Microsoft.PowerShell.Commands.Internal.Win32Native'
      ).GetMethod(
        'GetMessage', [BindingFlags]40
      ).Invoke(
        $null, @($ErrorCode)
      )
    }
    
    function private:Invoke-FreeLibrary {
      param(
        [Parameter(Mandatory=$true)]
        [IntPtr]$ModuleHandle
      )
      
      [void][Linq.Enumerable].Assembly.GetType(
        'Microsoft.Win32.UnsafeNativeMethods'
      ).GetMethod(
        'FreeLibrary', [BindingFlags]40
      ).Invoke($null, @($ModuleHandle))
    }
    
    function private:Get-ProcAddress {
      param(
        [Parameter(Mandatory=$true, Position=0)]
        [ValidateNotNullOrEmpty()]
        [String]$Module,
        
        [Parameter(Mandatory=$true, Position=1)]
        [ValidateNotNullOrEmpty()]
        [String]$Function
      )
      
      [Data.Rule].Assembly.GetType(
        'System.Data.Common.SafeNativeMethods'
      ).GetMethods(
        [BindingFlags]40
      ) | Where-Object {
        $_.Name -cmatch '\AGet(ProcA|ModuleH)'
      } | ForEach-Object {
        Set-Variable $_.Name $_
      }
      
      if (($ptr = $GetModuleHandle.Invoke(
        $null, @($Module)
      )) -eq [IntPtr]::Zero) {
        if (($mod = [Regex].Assembly.GetType(
          'Microsoft.Win32.SafeNativeMethods'
        ).GetMethod('LoadLibrary').Invoke(
          $null, @($Module)
        )) -eq [IntPtr]::Zero) {
          Write-Warning "$(Get-LastError)"
          break
        }
        $ptr = $GetModuleHandle.Invoke($null, @($Module))
      }
      
      $GetProcAddress.Invoke($null, @($ptr, $Function)), $mod
    }
    
    function private:Set-Delegate {
      param(
        [Parameter(Mandatory=$true, Position=0)]
        [ValidateScript({$_ -ne [IntPtr]::Zero})]
        [IntPtr]$ProcAddress,
        
        [Parameter(Mandatory=$true, Position=1)]
        [ValidateNotNullOrEmpty()]
        [String]$Delegate
      )
      
      $proto = Invoke-Expression $Delegate
      $method = $proto.GetMethod('Invoke')
      
      $returntype = $method.ReturnType
      $paramtypes = $method.GetParameters() |
                                    Select-Object -ExpandProperty ParameterType
      
      $holder = New-Object Reflection.Emit.DynamicMethod(
        'Invoke', $returntype, $paramtypes, $proto
      )
      $il = $holder.GetILGenerator()
      0..($paramtypes.Length - 1) | ForEach-Object {
        $il.Emit([OpCodes]::Ldarg, $_)
      }
      
      switch ([IntPtr]::Size) {
        4 { $il.Emit([OpCodes]::Ldc_I4, $ProcAddress.ToInt32()) }
        8 { $il.Emit([OpCodes]::Ldc_I8, $ProcAddress.ToInt64()) }
      }
      $il.EmitCalli(
        [OpCodes]::Calli, [CallingConvention]::StdCall, $returntype, $paramtypes
      )
      $il.Emit([OpCodes]::Ret)
      
      $holder.CreateDelegate($proto)
    }
    
    $ptr, $mod = Get-ProcAddress iphlpapi SendARP
    $SendARP = Set-Delegate $ptr `
                              '[Func[UInt32, UInt32, [Byte[]], [Byte[]], Int32]]'
    
    $key = 'HKLM:\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters\Interfaces\*'
    Get-ItemProperty $key | Where-Object {
      $_.DhcpIpAddress -and $_.DhcpIpAddress -ne '0.0.0.0'
    } | ForEach-Object {
      $inet_addr = [Regex].Assembly.GetType(
        'System.Net.UnsafeNclNativeMethods+OSSOCK'
      ).GetMethod('inet_addr', [BindingFlags]40)
    }{
      $adr = [BitConverter]::ToUInt32(
        [BitConverter]::GetBytes(
          $inet_addr.Invoke($null, @($_.DhcpIpAddress)
        )), 0
      )
      $mac = New-Object Byte[] 6
      $len = [BitConverter]::GetBytes($mac.Length)
      
      if (($ret = $SendARP.Invoke($adr, 0, $mac, $len)) -ne 0) {
        Write-Warning "$(Get-LastError $ret)"
        break
      }
      
      New-Object PSObject -Property @{
        Id         = $_.PSChildName
        MACAddress = ($mac | ForEach-Object {'{0:X2}' -f $_}) -join '-'
      }
    }
    
    if ($mod) { Invoke-FreeLibrary $mod }
    $collect | ForEach-Object { [void]$ta::Remove($_) }
  }
}
