i'm trying to batch edit lot of photos that i have saved on an HDD. Basically i need to change the property of the Date Taken so that i can index them. I will use the name of the file to get the acutal date.
I'm writing the script with Powershell and i can't get it to work with the .save() command at the end.
Here what i wrote so far
[reflection.assembly]::LoadWithPartialName("System.Drawing")
$filePath = 'imagePath.jpg'
# Load the image as a copy
$originalPic = New-Object System.Drawing.Bitmap($filePath)
$pic = $originalPic.Clone()
# Dispose the original bitmap object to release the file handle
$originalPic.Dispose()
$originalPic = $null
$props = @("306", "20531", "36867", "36868")
$defaultDateTime = Get-Date "2018-06-28"
$dateString = $defaultDateTime.ToString("yyyy:MM:dd HH:mm:ss`0")
$byteArray = [System.Text.Encoding]::ASCII.GetBytes($dateString)
foreach($prop in $props){
try{
$propertyItem = $pic.GetPropertyItem($prop)
}
catch{
$propertyItem = [System.Runtime.Serialization.FormatterServices]::GetUninitializedObject([System.Drawing.Imaging.PropertyItem])
$propertyItem.Id = $prop
}
$propertyItem.Type = 2
$propertyItem.Len = $byteArray.Length
$propertyItem.Value = $byteArray
$pic.SetPropertyItem($propertyItem)
}
$pic.Save($filePath) # Save the modified image with the new property
Everything it's working fine except the last step where i overwrite it. I understand that i could give a different name and that does indeed work but i don't want to create a copy for each image.
I thought that with the .clone i would obtain a copy and after the dispose, it would just close the object but it seems like it's always there.
This is what i get as error
Exception calling "Save" with "1" argument(s): "A generic error occurred in GDI+."
At C:\Users\user\Desktop\Random Tools\Powershell\FixMyPic.ps1:40 char:1
+ $pic.Save($filePath) # Save the modified image with the new property
+ ~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (:) [], MethodInvocationException
+ FullyQualifiedErrorId : ExternalException
Thanks everyone.
I also set it as $null after the dispose because it seems like it's just getting rid of the properties but the object is still there (i guess it's normal) but i thought it would be even better let's say to set it null. Object still loaded after dispose.
I tried to follow on some of the .NET related issues, they make use of the "using" but i don't think it will change much.
I tried opening and closing the ISE as per this question
EDIT:
i can also see that even with the use of dispose the image is still being held by Powershell and that's what's making it stop from overwriting
Your current approach - passing a file path to the
[Bitmap]constructor and then subsequently cloning the resulting object - appears to also clone the file handle created, resulting in a sharing violation when you call$pic.Save, as$picitself now has a read-lock on the same file.Under the hood all the file system I/O is handled by Windows' native GDI+ API, so you get a "generic GDI+ error" because it couldn't complete the file write and doesn't know that its because the calling application has an exclusive handle on the file.
To work around this, load the original picture with
[Image]::FromFile, then pass the resulting image object to the[Bitmap]constructor - at which point the constructor will copy the image data from the in-memory object rather than recreating the file handle, and you should be free to obtain a write handle to the file after disposing the original:As an aside, I'd recommend wrapping your code in a
try/finallystatement to ensure file system resources are always disposed of: