How do I edit the values of imported CSV variable with PowerShell

234 views Asked by At

The below script is an example of me importing a CSV file, trying to edit one of the values, then checking the value.

$Animal_Farm = Import-CSV "Test.csv"

Echo "The Data"
$Animal_Farm

Echo "`n`n`n"

Echo "Dog's Status"
$Animal_Farm.Status[1]
Echo "`n`n`n"

Echo "Updating Dog's Status to Employed"
$Animal_Farm.Status[1] = "Employed"
Echo "`n`n`n"

Echo "Dog's Status"
$Animal_Farm.Status[1]

This is the output, the data is unchanged and Dog's status is still Redundant.

The Data

Animal Ocupation    Status   
------ ---------    ------   
Cat    Construction Employed 
Dog    Professional Redundant
Rat    GP           Employed 




Dog's Status
Redundant




Updating Dog's Status to Employed




Dog's Status
Redundant

How do I edit the data imported? My plan is to feed the modified data into a JSON file.

This is the CSV file contents

Animal,Ocupation,Status
Cat,Construction,Employed
Dog,Professional,Redundant
Rat,GP,Employed
2

There are 2 answers

0
Mathias R. Jessen On BEST ANSWER

$Animal_Farm holds an array of objects, each of which have a Status property.

When you ask PowerShell to resolve $Animal_Farm.Status, PowerShell goes "huh, the $Animal_Farm array doesn't have a Status property, let me create a new array from the Status property values of each item in the array", which is what you eventually index into with $Animal_Farm.Status[1].

To address the properties of one of the underlying items in the original array, use the index operator directly on $Animal_Farm instead:

$Animal_Farm[1].Status
0
mklement0 On

To complement Mathias R. Jessen's helpful answer with conceptual information:

As Mathias states, accessing a property (.Status) on a collection (array) of objects ($Animal_Farm) automatically returns the property values of the collection's elements in an array[1] (assuming the collection doesn't itself have a property by this name, in which case the latter takes precedence).

This feature, which also works with methods, is called member-access enumeration, and is explained in more detail in this answer.

Member-access enumeration does not support assigning to properties, however:

If you tried something like $Animal_Farm.Status = 'Employed' in an attempt to set the status property for all animals, you'd get a somewhat surprising error, stating the collection $Animal_Farm has no .Status property.

  • The surprising error message (which is technically correct) notwithstanding, this inability to assign a (by definition uniform) value to a given property of all individual elements of a collection is by design.
  • By contrast, calling a mutating method via member-access enumeration - if available - is possible.
  • For more information, see this answer and GitHub issue #5271.

If you attempt to assign a property value via indexing ([...]) into an array of values obtained with member-access enumeration ($Animal_Farm.Status), e.g. $Animal_Farm.Status[1] = "Employed", that assignment is quietly ignored.

  • The reason is that $Animal_Farm.Status returns an array of property values that are no longer connected to the objects they came from, and while you can technically modify this array by assigning to its elements, the modified array is simply discarded after the assignment statement, given that it isn't captured anywhere.
    In short: You're mistakenly modifying a temporary array.

  • By contrast, applying the index to the collection itself ($Animal_Farm[1] to return the 2nd object stored in the collection) returns an object reference whose .Status property you can then effectively modify ($Animal_Farm[1].Status = "Employed")


Simplified example:

# Create a 2-element sample array.
$animals = [pscustomobject] @{ Animal = 'Cat'; Status = 'Employed' },
           [pscustomobject] @{ Animal = 'Dog'; Status = 'Redundant' } 


# Trying to set the status of ALL animals via member-access enumeration
# CAUSES AN ERROR, because it isn't supported:
#     InvalidOperation: The property 'Status' cannot be found on this object. [...]
$animals.Status = 'Employed'    # !! ERROR


# Trying to set the status of ONE animal via member-access enumeration is
# QUIETLY IGNORED:
#  * What is being modified is a *temporary array* of property values.
#  * Therefore, $animals.Status[1] is still 'Redundant' after this statement.
$animals.Status[1] = 'Employed' # !! QUIETLY IGNORED

# OK: Avoid member-access enumeration, and index directly into the collection
#     to get the animal object of interest, then set its property:
$animals[1].Status = 'Employed' # OK

[1] Technically, an array of values is only returned if the input collection has two or more elements; for a single-element collection, that one element's property value is returned as-is. In other words: member-access enumeration behaves like the PowerShell pipeline; see this answer and this answer for examples of associated pitfalls, and GitHub issue #6802 for a discussion of this perhaps surprising behavior.