PeterExif.ps1



Add-Type -AssemblyName System.Drawing

function Get-ExifContents {
    param(
        [Parameter(Mandatory)]
        $ImageStream, 
        [Parameter(Mandatory)]
        [int] $ExifCode,
        [Switch] $Numeric,
        [int] $Size = 0,   
        [int] $Parts = 1
    )

    Try {
        $list_id = $ImageStream.PropertyIdList
        if ($list_id.IndexOf($ExifCode) -eq -1) {
            if ($Numeric) {
                $Value = 0     
            } else {
                $Value = ""     
            }
        } else {

            if ($Numeric -and $Size -eq 0) {
                $Size = 8
            }

            $PropertyItem = $ImageStream.GetPropertyItem($ExifCode)
            if ($null -eq $PropertyItem) {
                if ($Numeric) {
                    $Value = 0     
                } else {
                    $Value = ""     
                }
            } else {
                $valueBytes = $PropertyItem.Value
                if ($null -eq $valueBytes) {
                    if ($Numeric) {
                        $Value = 0     
                    } else {
                        $Value = ""     
                    }
                } else {
                    if ($Numeric) {
                        $Value = MakeNumber -Num $valueBytes -Size $Size -Parts $Parts
                    } else {
                        $value = ""
                        0..($valueBytes.Length-1) | ForEach-Object {
                            if ($valueBytes[$_] -ne 0) {
                                $value = $value + [System.Text.Encoding]::ASCII.GetString($valueBytes[$_])
                            }
                        }
                        if ($null -ne $value -and $Size -gt 0 -and $Size -lt $Value.Length) {
                            $Value = $Value.Substring(0,$Size)
                        }
                    }
                }
            }

        }
    }
    Catch{
        if ($Numeric) {
            Write-Host "Type $($valueBytes.GetType())"
            Write-Host "Error in exif: $_"
        } else {
            Write-Host "Error in exif: $_"
        }
        $Value = ""     
    }

    return $Value
}

function Get-ByteMultiplier {
    param(
        [Parameter(Mandatory)]
        [int] $Factor
    )

    $byteMultiplier = 1
    1..$Factor | ForEach-Object {
        $byteMultiplier = $byteMultiplier * 256
    }

    return $byteMultiplier
}

function MakeNumber {
    param(
        [Parameter(Mandatory)]
        [byte[]] $Num,
        [int] $Size,
        [int] $Parts = 1
    )

    if ($null -eq $Num) {
        return "<null>"
    }

    if ($Num.Length -eq $Size -and $Parts -eq 1) {
        if ($Size -eq 1) {
            return ($Num[0])
        }
        if ($Size -eq 2) {
            return ( $Num[0] + 256 * $Num[1] )
        }
    }

    # GPS cords
    if ($Num.Length -eq 24 -and $Parts -eq 3) {
        $First =$Num[0] + (Get-ByteMultiplier 1) * $Num[1] + (Get-ByteMultiplier 2) * $Num[2] + (Get-ByteMultiplier 3) * $Num[3] ;
        $Second=$Num[8] + (Get-ByteMultiplier 1) * $Num[9] + (Get-ByteMultiplier 2) * $Num[10] + (Get-ByteMultiplier 3) * $Num[11] ; 
        $Third=$Num[16] + 256 * $Num[17] + 65536 * $Num[18] + 16777216 * $Num[19] ; 
        return @($first, $second, $third)
    }


    # Shutter
    if ($Num.Length -eq 8 -and $Parts -eq 2) {
        $First =$Num[0] + (Get-ByteMultiplier 1) * $Num[1] + (Get-ByteMultiplier 2) * $Num[2] + (Get-ByteMultiplier 3) * $Num[3] ;
        $Second=$Num[4] + (Get-ByteMultiplier 1) * $Num[5] + (Get-ByteMultiplier 2) * $Num[6] + (Get-ByteMultiplier 3) * $Num[7] ; 
        if ($first -gt 2147483648) {
            $first = $first  - (Get-ByteMultiplier 4)
        } 
        if ($Second -gt 2147483648) {
            $Second= $Second - (Get-ByteMultiplier 4)
        }
        if ($Second -eq 0) {
            $Second= 1
        } 
    
        if (($first -eq 1) -and ($Second -ne 1)) {
            $first = "1"
        } 
    
        return @($first, $second)
    }


    $First =$Num[0] + (Get-ByteMultiplier 1) * $Num[1] + (Get-ByteMultiplier 2) * $Num[2] + (Get-ByteMultiplier 3) * $Num[3] ;
    return $first

}


function Get-ImageFileExif {
    param(
        [Parameter(Mandatory)]
        [String] $ImageFile
    )

    if (!(Test-Path -Path $ImageFile)) {
        Write-Host "File not found: $ImageFile"
        break
    }
    Try {

        $fullPath = (Resolve-Path $ImageFile).Path

        $fileStreamArgs = @($fullPath
            [System.IO.FileMode]::Open
            [System.IO.FileAccess]::Read
            [System.IO.FileShare]::Read
            1024,
            [System.IO.FileOptions]::SequentialScan
        )

        Try {
            $fs = New-Object System.IO.FileStream -ArgumentList $fileStreamArgs
            $image = [System.Drawing.Image]::FromStream($fs)
        } Catch {
            # Error likely because not an image file
            return $null
        }

        $val = Get-ExifContents -ImageStream $image -ExifCode 37378 -Numeric -Size 8 -Parts 2
        if ($null -eq $val -or $val -eq "") {
            $Aperture = ""
        } else {
            if ($val.Length -eq 2) {
                $Aperture = "$($val[0]/$val[1])" 
            } else {
                $Aperture = $val[0]
            }
        }

        # Flash
        $val = Get-ExifContents -ImageStream $image -ExifCode 37385 -Numeric -Size 2
        if (($val % 2) -eq 1){
            $Flash = $true
        } else {
            $Flash = $false
        }

        # Shutterspeed
        $val = Get-ExifContents -ImageStream $image -ExifCode 33434 -Numeric -Size 8 -Parts 2
        if ($null -eq $val -or $val -eq "") {
            $Shutterspeed = ""
        } else {
            if ($val.Length -eq 2) {
                $Shutterspeed = "$($val[0])/$($val[1])" 
            } else {
                $Shutterspeed = $val[0]
            }
        }

        # Latitude
        $val = Get-ExifContents -ImageStream $image -ExifCode 2 -Numeric -Size 24 -Parts 3
        if ($null -eq $val -or $val -eq "") {
            $Latitude = ""
        } else {
            if ($val.Length -eq 3) {
                $Latitude = "$($val[0]).$($val[1]).$($val[2])" 
            } else {
                $Latitude = $val[0]
            }
        }
        # Longitude
        $val = Get-ExifContents -ImageStream $image -ExifCode 4 -Numeric -Size 24 -Parts 3
        if ($null -eq $val -or $val -eq "") {
            $Longitude = ""
        } else {
            if ($val.Length -eq 3) {
                $Longitude = "$($val[0]).$($val[1]).$($val[2])" 
            } else {
                $Longitude = $val[0]
            }
        }


        $ExifData = [PSCustomObject][ordered]@{
            File = $ImageFile

            DateTaken = Get-ExifContents -ImageStream $image -ExifCode 36867 -Size 19
            DateDigitized = Get-ExifContents -ImageStream $image -ExifCode 36868 -Size 19
            DateModified = Get-ExifContents -ImageStream $image -ExifCode 306 -Size 19

            Author = Get-ExifContents -ImageStream $image -ExifCode 40093
            Title = Get-ExifContents -ImageStream $image -ExifCode 40091 #270
            Subject = Get-ExifContents -ImageStream $image -ExifCode 40095
            Comments = Get-ExifContents -ImageStream $image -ExifCode 40092 #37510
            Keywords = Get-ExifContents -ImageStream $image -ExifCode 40094

            Artist = Get-ExifContents -ImageStream $image -ExifCode 315
            Copyright = Get-ExifContents -ImageStream $image -ExifCode 33432

            Height = Get-ExifContents -ImageStream $image -ExifCode 40963 -Numeric
            Width = Get-ExifContents -ImageStream $image -ExifCode 40962 -Numeric
            PixelX = Get-ExifContents -ImageStream $image -ExifCode 40962 -Numeric -Size 8
            PixelY = Get-ExifContents -ImageStream $image -ExifCode 40963 -Numeric -Size 8
            ResolutionX = Get-ExifContents -ImageStream $image -ExifCode 282 -Numeric
            ResolutionY = Get-ExifContents -ImageStream $image -ExifCode 283 -Numeric

            CameraMaker = Get-ExifContents -ImageStream $image -ExifCode 271
            CameraModel = Get-ExifContents -ImageStream $image -ExifCode 272
            CameraLabel = Get-ExifContents -ImageStream $image -ExifCode 51105
            SoftwareVersion = Get-ExifContents -ImageStream $image -ExifCode 305

            LatitudeRef = Get-ExifContents -ImageStream $image -ExifCode 1
            Latitude = $Latitude
            LongitudeRef = Get-ExifContents -ImageStream $image -ExifCode 3
            Longitude = $Longitude

            ExifVersion = Get-ExifContents -ImageStream $image -ExifCode 36864

            Flash = $Flash
            Iso = Get-ExifContents -ImageStream $image -ExifCode 34855 -Numeric 
            FocalLength = Get-ExifContents -ImageStream $image -ExifCode 37386 -Numeric -Size 2
            ShutterSpeed = $Shutterspeed
            Aperture = $Aperture
            FNumber = Get-ExifContents -ImageStream $image -ExifCode 33437 -Numeric -Size 4

        }

        $image.dispose()
        $fs.Close()

        return $ExifData
    }
    Catch {
        Write-Host "Error: $_"
        Write-Error "Error Opening '$ImageFile'"
        if ($image) {
            $image.dispose()
        }
        if ($fs) {
            $fs.close()
        }
        return $null
    }
}



function Set-ExifCsvHeader {

    
    $ExifData = [PSCustomObject][ordered]@{
        File = ""

        DateTaken = ""
        DateDigitized = ""
        DateModified = ""

        Author = ""
        Title = ""
        Subject = ""
        Comments = ""
        Keywords = ""

        Artist = ""
        Copyright = ""

        Height = 0
        Width = 0
        PixelX = 0
        PixelY = 0
        ResolutionX = 0
        ResolutionY = 0

        CameraMaker = ""
        CameraModel = ""
        CameraLabel = ""
        SoftwareVersion = ""

        LatitudeRef = ""
        Latitude = ""
        LongitudeRef = ""
        Longitude = ""

        ExifVersion = ""

        Flash = $false
        Iso = 0
        FocalLength = 0
        ShutterSpeed = ""
        Aperture = 0
        FNumber = 0

    }

    $exifRecord = ''
    $first = $true
    $ExifData.PSObject.Properties | foreach-object {
        if ($first) {
            $exifRecord = '"' + $_.Name + '"'
        } else{
            $exifRecord = $exifRecord + ',"' + $_.Name + '"'
        }
        $first = $false
    }

    return $exifRecord

}

function Set-ExifCsvRecord {
    param(
        [Parameter(Mandatory)]
        [PSCustomObject] $ExifData
    )

    $exifRecord = ''
    $first = $true
    $ExifData.PSObject.Properties | foreach-object {
        if ($first) {
            $exifRecord = '"' + $_.value + '"'
        } else{
            $exifRecord = $exifRecord + ',"' + $_.value + '"'
        }
        $first = $false
    }

    return $exifRecord

}