EXIF data with PowerShell (Part 4)

So in my last couple of posts, I talked about how to pull EXIF tags from JPGs using PowerShell, read the latitude and longitude, and how to script pulling the data from JPGs or a list of JPGs in a directory and outputting it to either the screen or to a CSV. In this post, I will go over how to remove the properties from the metadata exposed in the script.
While I was looking at the .NET System.Drawing.Image class I first noticed the SetPropertyItem method. My instinct was to use that method to clear the property values from the JPG. Here is the default value of a test JPG with tag 271 which shows that my camera maker is Huawei.

So now that I have set the manufacturer tag value, I want to update it to an empty byte array. To do that I need to save the entire tag into a variable and then update the value of the tag with a byte array. The screenshot below shows the update value in the tag 271.

Once the tag has been updated the image file needs to be updated.

The first line creates a .NET buffer in memory to read the image file into temporarily so that it can be saved, which is accomplished by the Image Class Save method. The method requires the first parameter to be the stream to read into and the other parameter is the format of the image to be saved. At that point we dispose of the $image object and then close the file stream, $fs we need to do this to be able to save the new file with the same file name.  We then create a new file stream to read the memory buffer into, we pass the path to the file we want to write to and the stream needs to be opened with the ability to create files which is the second parameter. We then write the memory stream to the file stream and flush the file stream to the file system and close the stream.

So now that we’ve written a new file with the updated metadata let’s inspect it.

And that’s not the output I expected, it put a null byte as the first character and moved the next tag’s 272 value into the manufacturer’s tag. Further experimentation is required to return the results that I want.

The above output is from the Get-Member cmdlet which shows all the properties and methods of the object. To call Get-Member pipe the results of a command into it. What caught my eye is the RemovePropertyItem method which takes just an integer input of the property id. So I tried it with the following results:

I saved the file in the same manner as earlier and then attempted to retrieve the property value from the metadata. We received a Property cannot be found exception, normally exceptions are not desired while writing code but in this case, it shows that I successfully removed the property value from JPG. At this point, I wrote a function to remove all the property items listed in a list inside the function.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
Function Set-FileContents {
     param(
          [string]$file
     )
     $exifCodes = (1, 2, 3, 4, 6, 271, 272, 305, 306)
     Try {
          $fullPath = (Get-ChildItem $file).fullname
          $fs = [System.IO.File]::OpenRead($fullPath)
          $image = [System.Drawing.Image]::FromStream($fs, $false, $false)
     }
     Catch {
          Write-Output "Error Opening File: $file"
          return
     }
     foreach($exifCode in $exifCodes){
          Try {
               $image.RemovePropertyItem($exifCode)
          }
          Catch {
               continue
          }
     }
     $memoryStream = New-Object System.IO.MemoryStream
     $image.Save($memoryStream, $image.RawFormat)
     $image.Dispose()
     $fs.Close()
     $writer = New-Object System.IO.FileStream($fullPath, [System.IO.FileMode]::Create)
     $memoryStream.WriteTo($writer)
     $writer.Flush()
     $writer.Close()

}

I used a predefined list without labels because I don’t care what the label is when removing the metadata, it won’t be there afterwords and doesn’t work. To integrate the clean function into the script, I had to update the parameters passed into the script upon invocation by modifying the parameter block and adding a loop to parse the $argsList.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
param(
     [string]$file = $false,
     [string]$exportcsv = $false,
     [Parameter(Position = 0, ValueFromRemainingArguments = $true)]$args
)

# Loads the System.Drawing DLL for usage
Add-Type -Assembly System.Drawing
# Write-Host "Args: $args"

$clean = $false
$argsList = $args -split(' ')
foreach($arg in $argsList){
     if ( ($arg -like "clean") -or ($arg -like "-clean")){
          $clean = $true
     }
}

And then added an if block in the if ($isDir) and else blocks.

1
2
3
4
5
if ($clean){
     Set-FileContents($childFile)
     $obj = Get-FileContents($childFile)
     $exportArray.add($obj) | Out-Null
}

At this point, we have a fully functioning script that will pull the metadata from a JPG and remove it if desired.  I will post the script on my GitHub page and update this post to link to it.

Leave comment

Your email address will not be published. Required fields are marked with *.