How to read metadata easily in powershell with TagLibSharp
Tags: beginners, powershell
Published: 2021-03-21 11:56:24

---
title: How to read metadata easily in powershell with TagLibSharp
published: true
date: 2021-03-21 11:56:24 UTC
tags: beginner, powershell
canonical_url: https://alexandrubucur.com/blog/2021/how-to-read-metadata-easily-in-powershell-with-taglibsharp/
---
Doing my quarterly phone image cleanup and backup on onedrive I noticed that [the script](https://gist.github.com/CoolGoose/75462ff5cfd6355153f4526746e09e2b/revisions#diff-e162f571070996842f69bb345df3949ac5e415fad0aca772bc18d59efdef3e2d) I was using wasn't quite consistent on the folder year and month generation.

If you think of it it does make sense, since it's basically doing a smart workaround on using `LastWriteTime` for its date source, but that isn't always good enough.

After some hours googling around (not yet on a 'binge' :D) I got two options that looked great.

1. Reading the __Date Taken__ from the file using `ComObject`
2. Reading it using __[TagLibSharp](https://github.com/mono/taglib-sharp)__

Reading the __Date Taken__ from the file using _ComObject_
-

```powershell
  $shellObject = New-Object -ComObject Shell.Application
  $directoryObject = $shellObject.Namespace($file.Directory.FullName)
  $fileObject = $directoryObject.ParseName($file.Name)

  $property = 'Date taken'
  for (
    $index = 5;
    $directoryObject.GetDetailsOf($directoryObject.Items,$index) -ne $property;
    ++$index
    ) {}

  $dateString = $directoryObject.GetDetailsOf($fileObject,$index)
```

The problem for me was that I couldn't parse that date string to an actual DateTime object no matter what I tried (`[datetime]::Parse` / `[datetime]::ParseExact` with various formatting, even if `g` should have been a match, various cultures etc (including just sending `$null`)).

If anyone has any ideas on how to fix this I'm really curious.

Reading it using _[TagLibSharp]_
-

The tricky part for me here was understanding how to load a c# dll in powershell.
If you install the package using _nuget_ the easiest way to figure out where the package is installed is to run `Get-Package TagLibSharp | Format-List -Property Source`. 

Your output should be similar to:
```
❯ Get-Package TagLibSharp | Format-List -Property Source

Source : C:\Program Files\PackageManagement\NuGet\Packages\TagLibSharp.2.2.0\TagLibSharp.2.2.0.nupkg
```

Once we know that we can use the following code to load the dll in our powershell script:

```powershell
    $TagLib = "C:\Program Files\PackageManagement\NuGet\Packages\TagLibSharp.2.2.0\lib\netstandard2.0\TagLibSharp.dll"
    Add-Type -Path $TagLib
    
    [System.Reflection.Assembly]::LoadFile($TagLib)
```

The reading part is really easy, and for images the property you want to look for is `ImageTag`:
```powershell
$tag = [TagLib.File]::Create($file)
$tag.ImageTag
```

There are a few nice things to gain once using TagLibSharp.

1. `ImageTag` has a `DateTime` object directly available.
2. You have full access to the image metadata so you could for eg create folders automatically based on the latitude and longitude with the closest city name. In case this sounds interesting please let me know and I'll try doing an updated version that does just that.
3. We can implement a fallback to the simple `Tag` for videos for eg.



Full version can be found below and in the [gist](https://gist.github.com/CoolGoose/75462ff5cfd6355153f4526746e09e2b).

```powershell
    $TagLib = "C:\Program Files\PackageManagement\NuGet\Packages\TagLibSharp.2.2.0\lib\netstandard2.0\TagLibSharp.dll"
    Add-Type -Path $TagLib
    
    [System.Reflection.Assembly]::LoadFile($TagLib)
    
    # Get the files which should be moved, without folders
    $files = Get-ChildItem 'X:\OneDrive\Pictures' -Recurse | Where-Object { !$_.PSIsContainer }
    
    # List Files which will be moved
    $files
    
    # Target Filder where files should be moved to. The script will automatically create a folder for the year and month.
    $targetPath = 'X:\OneDrive\Photos'
    
    foreach ($file in $files) {
      $tag = [TagLib.File]::Create($file)
    
      # Get year and Month of the file
      if ($tag.ImageTag.DateTime) {
        $year = $tag.ImageTag.DateTime.Year.ToString()
        # add 0 prefix to month
        $month = $tag.ImageTag.DateTime.Month.ToString("00")
      }
      elseif ($tag.Tag.DateTime) {
        $year = $tag.Tag.DateTime.Year.ToString()
        # add 0 prefix to month
        $month = $tag.Tag.DateTime.Month.ToString("00")
      }
      else {
        # I used LastWriteTime since this are synced files and the creation day will be the date when it was synced
        $year = $file.LastWriteTime.Year.ToString()
        # add 0 prefix to month
        $month = $file.LastWriteTime.Month.ToString("00")
      }
    
      # Out FileName, year and month
      $file.Name
      $year
      $month
    
      # Set Directory Path , change as wanted
      $Directory = $targetPath + "\" + $year + "\" + $year + "-" + $month
      # Create directory if it doesn't exists
      if (!(Test-Path $Directory)) {
        New-Item $directory -type directory
      }
    
      # Move File to new location, remove -Force if you might have files with the same name, i've used it since i knew that duplicates are ok to overwrite
      $file | Move-Item -Destination $Directory -Force
    }
```