Error de Powershell "Se produjo un error genérico en GDI+". al intentar sobrescribir la imagen después de cambiar sus propiedades

Resuelto AndyMcMars asked hace 9 meses • 0 respuestas

Estoy intentando editar por lotes muchas fotos que he guardado en un disco duro. Básicamente, necesito cambiar la propiedad de Fecha de toma para poder indexarlas. Usaré el nombre del archivo para obtener la fecha actual.

Estoy escribiendo el script con Powershell y no consigo que funcione con el comando .save() al final.

Aquí lo que escribí hasta ahora.

[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

Todo funciona bien excepto el último paso donde lo sobrescribo. Entiendo que podría dar un nombre diferente y eso funciona, pero no quiero crear una copia para cada imagen.

Pensé que con el .clone obtendría una copia y después de eliminarlo, simplemente cerraría el objeto pero parece que siempre está ahí.

Esto es lo que me sale como 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

Gracias a todos.

También lo configuré como $null después de eliminarlo porque parece que simplemente se está deshaciendo de las propiedades, pero el objeto todavía está allí (supongo que es normal), pero pensé que sería aún mejor, digamos, establecerlo como nulo. Objeto aún cargado después de eliminarlo .

Intenté seguir algunos de los problemas relacionados con .NET, hacen uso del "uso", pero no creo que cambie mucho.

Intenté abrir y cerrar el ISE según esta pregunta

EDITAR:

También puedo ver que incluso con el uso de desechar, Powershell todavía retiene la imagen y eso es lo que impide que se sobrescriba.

ingrese la descripción de la imagen aquí

AndyMcMars avatar Feb 16 '24 17:02 AndyMcMars
Aceptado

Su enfoque actual (pasar una ruta de archivo al [Bitmap]constructor y luego clonar el objeto resultante) parece clonar también el identificador de archivo creado, lo que resulta en una infracción de uso compartido cuando llama a $pic.Save, ya $picque ahora tiene un bloqueo de lectura en el mismo archivo.

$originalPic = New-Object System.Drawing.Bitmap($filePath) # exclusive read handle count: 1
$pic = $originalPic.Clone()                                # exclusive read handle count: 2

$originalPic.Dispose()                                     # exclusive read handle count: 1
$originalPic = $null                                       # still not in the clear...

En el fondo, toda la E/S del sistema de archivos es manejada por la API GDI+ nativa de Windows, por lo que obtiene un "error genérico de GDI+" porque no pudo completar la escritura del archivo y no sabe que se debe a que la aplicación que llama tiene una mango exclusivo en el archivo.

Para solucionar este problema, cargue la imagen original con [Image]::FromFile, luego pase el objeto de imagen resultante al [Bitmap]constructor; en ese momento, el constructor copiará los datos de la imagen del objeto en memoria en lugar de recrear el identificador del archivo, y usted debería poder hacerlo. obtenga un identificador de escritura para el archivo después de deshacerse del original:

$originalPic = [System.Drawing.Image]::FromFile($filePath) # exclusive read handle count: 1
$pic = [System.Drawing.Bitmap]::new($originalPic)          # exclusive read handle count: 1
$originalPic.Dispose()                                     # exclusive read handle count: 0

Además, recomendaría empaquetar su código en una declaración try/ finallypara garantizar que los recursos del sistema de archivos siempre se eliminen:

# Load assembly
Add-Type -AssemblyName System.Drawing

# Resolve file path
$filePath = Resolve-Path 'imagePath.jpg'

try {
    # Load image from file, copy contents to new image object
    $originalPic = [System.Drawing.Image]::FromFile($filePath)
    $pic = [System.Drawing.Bitmap]::new($originalPic)

    # Dispose the original bitmap object to release the file handle
    $originalPic.Dispose()
    $originalPic = $null

    # Update properties
    $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
}
finally {
    # clean up regardless of errors
    $originalPic, $pic | Where-Object { $_ -is [IDisposable] } | ForEach-Object Dispose
}
Mathias R. Jessen avatar Feb 16 '2024 11:02 Mathias R. Jessen