views:

1338

answers:

4

The following code gives me an array of PSCustomObjects, how can I get it to return an array of Strings?

$files = Get-ChildItem $directory -Recurse | Select-Object FullName | Where-Object {!($_.psiscontainer)}

(As a secondary question, what's the psiscontainer part for? I copied that from an example online)

Post-Accept Edit: Two great answers, wish I could mark both of them. Have awarded the original answer.

+5  A: 

You just need to pick out the property you want from the objects. FullName in this case.

$files = Get-ChildItem $directory -Recurse | Select-Object FullName | Where-Object {!($_.psiscontainer)} | foreach {$_.FullName}

Edit: Explanation for Mark, who asks, "What does the foreach do? What is that enumerating over?"

Sung Meister's explanation is very good, but I'll add a walkthrough here because it could be helpful.

The key concept is the pipeline. Picture a series of pingpong balls rolling down a narrow tube one after the other. These are the objects in the pipeline. Each stage of pipeline--the code segments separated by pipe (|) characters--has a pipe going into it and pipe going out of it. The output of one stage is connected to the input of the next stage. Each stage takes the objects as they arrive, does things to them, and sends them back out into the output pipeline or sends out new, replacement objects.

Get-ChildItem $directory -Recurse

Get-ChildItem walks through the filesystem creating FileSystemInfo objects that represent each file and directory it encounters, and puts them into the pipeline.

Select-Object FullName

Select-Object takes each FileSystemInfo object as it arrives, grabs the FullName property from it (which is a path in this case), puts that property into a brand new custom object it has created, and puts that custom object out into the pipeline.

Where-Object {!($_.psiscontainer)}

This is a filter. It takes each object, examines it, and sends it back out or discards it depending on some condition. Your code here has a bug, by the way. The custom objects that arrive here don't have a psiscontainer property. This stage doesn't actually do anything. Sung Meister's code is better.

foreach {$_.FullName}

Foreach, whose long name is ForEach-Object, grabs each object as it arrives, and here, grabs the FullName property, a string, from it. Now, here is the subtle part: Any value that isn't consumed, that is, isn't captured by a variable or suppressed in some way, is put into the output pipeline. As an experiment, try replacing that stage with this:

foreach {'hello'; $_.FullName; 1; 2; 3}

Actually try it out and examine the output. There are four values in that code block. None of them are consumed. Notice that they all appear in the output. Now try this:

foreach {'hello'; $_.FullName; $ x = 1; 2; 3}

Notice that one of the values is being captured by a variable. It doesn't appear in the output pipeline.

dangph
What does the foreach do? What is that enumerating over?
Mark Ingram
That's a great answer, dangph.
Sung Meister
+4  A: 

For Question #1

I have removed "select-object" portion - it's redundant and moved "where" filter before "foreach" unlike dangph's answer - Filter as soon as possible so that you are dealing with only a subset of what you have to deal with in the next pipe line.

$files = Get-ChildItem $directory -Recurse | Where-Object {!$_.PsIsContainer} | foreach {$_.FullName}

That code snippet essentially reads

  • Get all files full path of all files recursively (Get-ChildItem $directory -Recurse)
  • Filter out directories (Where-Object {!$_.PsIsContainer})
  • Return full file name only (foreach {$_.FullName})
  • Save all file names into $files

Note that for foreach {$.FullName}, in powershell, last statement in a script block ({...}) is returned, in this case $.FullName of type string

If you really need to get a raw object, you don't need to do anything after getting rid of "select-object". If you were to use Select-Object but want to access raw object, use "PsBase", which is a totally different question(topic) - Refer to "What's up with PSBASE, PSEXTENDED, PSADAPTED, and PSOBJECT?" for more information on that subject

For Question #2

And also filtering by !$.PsIsContainer* means that you are excluding a container level objects - In your case, you are doing Get-ChildItem on a FileSystem provider(you can see PowerShell providers through Get-PsProvider), so the container is a DirectoryInfo(folder)

PsIsContainer means different things under different PowerShell providers; e.g.) For Registry provider, PsIsContainer is of type Microsoft.Win32.RegistryKey Try this:

>pushd HKLM:\SOFTWARE
>ls | gm

[UPDATE] to following question: What does the foreach do? What is that enumerating over? To clarify, "foreach" is an alias for "Foreach-Object" You can find out through,

get-help foreach

-- or --

get-alias foreach

Now in my answer, "foreach" is enumerating each object instance of type FileInfo returned from previous pipe (which has filtered directories). FileInfo has a property called FullName and that is what "foreach" is enumerating over.
And you reference object passed through pipeline through a special pipeline variable called "$_" which is of type FileInfo within the script block context of "foreach".

Sung Meister
What does the foreach do? What is that enumerating over?
Mark Ingram
Let me update the answer
Sung Meister
+4  A: 

To get the string for the file name you can use

$files = Get-ChildItem $directory -Recurse | Where-Object {!($_.psiscontainer)} | Select-Object -ExpandProperty FullName

The -ExpandProperty parameter allows you to get back an object based on the type of the property specified.

Further testing shows that this did not work with V1, but that functionality is fixed as of the V2 CTP3.

Steven Murawski
Are you sure? It doesn't seem to work.
dangph
+2  A: 

For V1, add the following filter to your profile:

filter Get-PropertyValue([string]$name) { $_.$name }

Then you can do this:

gci . -r | ?{!$_.psiscontainer} | Get-PropertyName fullname

BTW, if you are using the PowerShell Community Extensions you already have this.

Regarding the ability to use Select-Object -Expand in V2, it is a cute trick but not obvious and really isn't what Select-Object nor -Expand was meant for. -Expand is all about flattening like LINQ's SelectMany and Select-Object is about projection of multiple properties onto a custom object.

Keith Hill