Michael
Michael

Reputation: 746

powershell script not enumerating add-member's scriptproperty input properly

My apologies if this is a dupe but after searching for some time, I wasn't able to find an answer that worked in my case.

I have a python script that searches for faces in photographs. The python script works fine and I'm now trying to incorporate its output into a csv file that I can use to run some statistics on.

Enter powershell.

Here is my powershell script:

$picts = gci *.jpg
$(foreach ($f in $picts) 
{
    $obj2 = New-Object System.Object       
    $obj2 | Add-Member -MemberType  NoteProperty -Name fileName -Value $f.Name
    $obj2 | Add-Member -MemberType ScriptProperty -Name datumN -Value { 
        python face_detect_batch.py  $f.Name 
        }
    $obj2
 }
) | export-csv t.csv

The python script prints a single line containing the filename it was fed along with some info about the photo.

I'm testing the script on a folder that has two jpegs in it, 1.jpg and 2.jpg. If I run the script by hand on each photo, it works as expected.

However, if I run the above script to iterate through the photos I get the following output:

#TYPE System.Object 
fileName    datumN

1.jpg   name: 2.jpg; blurScore: 269; facex: 68; facey: 84; faceWidth: 155; faceHeight:155
2.jpg   name: 2.jpg; blurScore: 269; facex: 68; facey: 84; faceWidth: 155; faceHeight:155

The output shows that the script iterates as I expect and adds the two distinct filenames to $obj2's filename property but when the script property add-member executes, my python script is fed 2.jpg both times.

I've tried feeding [ref]$f.name and $global:f.name in the value field of add-member but none of the changes make any difference - I always get 2.jpg being passed to my python script.

Why does the first reference to $f.Name enumerate the files as expected but the second reference only sees the last jpeg?

Upvotes: 2

Views: 90

Answers (1)

mklement0
mklement0

Reputation: 440412

Do not use a ScriptProperty member to create a property whose value should be assigned once, statically - instead, use a NoteProperty member:

Get-ChildItem *.txt | ForEach-Object {
  [pscustomobject] @{
    fileName = $_.Name
    datumN = python face_detect_batch.py $_.Name
  }
 } | Export-Csv t.csv
  • Note how [pscustomobject] @{ ... } is used to define a custom object indirectly via a hashtable literal, which simplifies its construction.

  • Value python face_detect_batch.py $_.Name for key / property datumN simply invokes python with every input filename at the time the object is constructed and stores the result in property datumN, as a one-time operation.

  • The properties of the resulting [pscustomobject] instance are implicitly and invariable of type NoteProperty, i.e., static values.


As for what you tried:

By creating a ScriptProperty member, you're assigning a piece of code that is run every time the property is accessed.

Thus, it is only later, at the time your objects are exported by Export-Csv that their .datumN property is retrieved, causing the script block to run then, at which point $f happens to contain the last iteration's value.

Upvotes: 2

Related Questions