Home > Software engineering >  What does this mean in PowerShell @{propName=value}
What does this mean in PowerShell @{propName=value}

Time:10-30

For a list of files (photos), I'm trying to check if they have EXIF info from a camera or not and only further process photos that aren't from a camera. I thought I would just check if the CameraModel EXIF prop is blank, but I'm stuck.

I have this code:

$scriptpath = $MyInvocation.MyCommand.Path
$dir = Split-Path $scriptpath

Write-Host $dir
Invoke-Expression -Command $($dir   "\Get-FileMetaData.ps1")

Get-ChildItem -Path $dir -Directory |
Foreach-Object {
    $date_str = $_.Name   '-01-01 00:00:00'
    $date = $(Get-Date -Date $date_str)
    Write-Host $_.FullName
    Get-ChildItem -Path $_.FullName -Filter "*.jpg" |
        Foreach-Object {
            Write-Host $_.Name
            $md = Get-FileMetaData -File $_.FullName
            $camModel = $($md | Select "CameraModel")
            Write-Host $($camModel | Select-Object -Property "CameraModel")
        }
}

Where Get-FileMetaData from from here https://evotec.xyz/getting-file-metadata-with-powershell-similar-to-what-windows-explorer-provides/ halfway down.

This returns

DSC_0132.JPG
@{CameraModel=NIKON D80}
ScannedImage001.JPG
@{CameraModel=}

My question is, how do I check if this is blank, as in the bottom result @{CameraModel=}? What does @{CameraModel=} actually mean? What is the @{} signify?

Edit: Thanks @mlkement0!

My final script is below, which sets the creation and last modified dates of non-camera JPGs to 1st Jan where the year is the year in the folder name:

$scriptpath = $MyInvocation.MyCommand.Path
$dir = Split-Path $scriptpath

Write-Host $dir
Invoke-Expression -Command $($dir   "\Get-FileMetaData.ps1")

Get-ChildItem -Path $dir -Directory |
Foreach-Object {
    $date_str = $_.Name   '-01-01 00:00:00'
    $date = $(Get-Date -Date $date_str)
    Write-Host $_.FullName
    Get-ChildItem -Path $_.FullName -Filter "*.jpg" |
        Foreach-Object {
            Write-Host $_.Name
            $md = Get-FileMetaData -File $_.FullName
            $camModel = $md.CameraModel
            if ($camModel -like ''){
                $(get-item $_.FullName).CreationTime = $date
                $(get-item $_.FullName).LastWriteTime = $date
                Write-Host $("Set date to "   $date)
            }
        }
}

CodePudding user response:

A few general points:

  • Write-Host is typically the wrong tool to use, unless the intent is to write to the display only, bypassing the success output stream and with it the ability to send output to other commands, capture it in a variable, or redirect it to a file. To output a value, use it by itself; e.g, $value, instead of Write-Host $value (or use Write-Output $value, but that is rarely needed); see this answer. To explicitly print only to the display but with rich formatting, use Out-Host.

  • Therefore, to get proper output formatting, just use $_.Name and $camModel | Select-Object -Property "CameraModel" as-is - no need for Write-Host or even Write-Output, which is implied. If you want just the camera model value, use -ExpandProperty instead of Property.


As for your specific questions:

how do I check if this is blank, as in the bottom result @{CameraModel=}

$md = Get-FileMetaData -File $_.FullName

# Just use property access to get the camera model *value*.
# If no such property exists, $null is returned
# (except if Set-StrictMode -Version 2 or above is in effect).
$camModel = $md.CameraModel 

if ($camModel -like '' {
  "Camera model is $null or the empty string."
}

What does @{CameraModel=} actually mean? What is the @{} signify?

This hashtable-like representation is unrelated to actual hashtables and not meant for programmatic processing.

PowerShell uses this for-display representation when [pscustomobject] instances, such as created by Select-Object, are coerced to strings, such as when you use Write-Host - see this answer for more information.


Optional reading: Pitfall when using Select-Object with -ExpandProperty for extracting property values:

The above solution uses simple property access (dot notation) to get the value of the $md object's .CameraModel property, i.e. $md.CameraModel

This syntactically simple approach even works when directly applied to expressions and commands enclosed in (...), and even when the object being operated on is a collection of objects, due to a feature called member-access enumeration); e.g., in the following example the .Year property values of the input objects are returned as an array ([object[]]):

((Get-Date), (Get-Date).AddYears(1)).Year # e.g. -> @(2022, 2023)

If the return values are to be collected in memory anyway, there is therefore no good reason to achieve the same tasks via a - invariably slower - Select-Object -ExpandProperty call; e.g.:

# Same result as above.
(Get-Date), (Get-Date).AddYears(1) | Select-Object -ExpandProperty Year

Another reason to avoid Select-Object -ExpandProperty is an inconsistency you've discovered:

Unlike with -Property, -ExpandProperty reports an error for any input object that happens not to have that property; e.g.:

# -> Outputs 1, for the first object, but then emits an 
#    ERROR: 'Select-Object: Property "Prop" cannot be found.'
[pscustomobject] @{ Prop=1 }, [pscustomobject] @{ } | 
  Select-Object -ExpandProperty Prop

With -Property, by contrast, you'd get a [pscsutomobject] instance whose .Prop value is $null for any input object that doesn't have a .Prop property - no error occurs. This surprising inconsistency is the subject of GitHub issue #18416.

Strict mode (Set-StrictMode) considerations:

  • As a cmdlet, Select-Object is not affected by the strict mode that is in effect:

    • The (unexpected) errors that occur with -ExpandProperty for non-existent properties are reported per input object (and are therefore non-terminating errors), which means that values are still reported for any input objects that do have the requested property.
  • By contrast, attempts to access a non-existent property (with ., the member-access operator) only cause an error if Set-StrictMode -Version 2 or higher is in effect:

    • Using . to access a non-existent property results in a statement-terminating error, which means that the entire statement is instantly terminated, with the attempted property access producing no (data) output.

    • Since this also applies to member-access enumeration, there is no (data) output even if some of the input objects have the targeted property - unlike with Select-Object -ExpandProperty; e.g.:

      # Set strict mode to a version that enforces property existence.
      Set-StrictMode -Version 2
      
      # !! This ONLY produces an ERROR - it doesn't also output
      # !! 1, even though the first input object has a .Prop property.
      ([pscustomobject] @{ Prop=1 }, [pscustomobject] @{ }).Prop
      
  • Related