Recently, my feelings towards PowerShell have been somewhat ambivalent. There are some seriously good ideas in the PowerShell design. I really want to like it, it’s just that it makes it very hard sometimes… Let’s just say you have the simple task to create a file that contains a list of all files in a specific directory – recursively of course. You’ll probably start with the Get-ChildItem cmdlet (aka “ls”) which gives you the files in the current directory.
$ Get-ChildItem
If you don’t already know how to, you will then most likely consult the documentation for Get-ChildItem to find out how to list files recursively
$ Get-ChildItem -recurse
This gives you file lists for each subdirectory. Now we have all the “value”, but the format is all wrong. By default PowerShell will try to optimize the output by sending it through one of its standard formatters ($ Get-Help Format-), but the problem with that approach is, that all of these formatters limit the output to the width of the PS window and tend to cut off everything that is too long. This also happens when you redirect the output to a file – long lines are still cut off which is rather unpleasant. Some unix command line tools also try to fit output to the terminal width, but at least those are usually aware of where exatcly the output is going and adapt the output accordingly [e.g. “ls –color=auto” only emits ASCII color control characters when connected to a terminal, NOT when the output is piped or redirected somewhere else].
Anyway, we’ll get there soon. First we have to get rid of superfluous stuff in the file listing. In the beginning of my PS time I simply used the ForEach-Object cmdlet for that. The Get-ChildItem cmdlet gives you a sequence of DirectoryInfo and FileInfo objects and all I want is the value of the FullName property, so I would do the following
$ Get-ChildItem -recurse | ForEach-Object { $_.FullName }
This works like a charm and completely solves all my problems with output formatting due to the fact that the output of the ForEach-Object cmdlet already is a sequence of strings and that does not get auto-formatted. My problem with this approach is that the ForEach-Object cmdlet as opposed to most other cmdlets “blocks” the pipeline, meaning that the ForEach is only executed after the preceding operation has completed instead of executing the block as each object gets passed through the pipeline. This is rather inefficient and for long operations this becomes a major headache because you can’t see any output until everything before the ForEach has completed.
After some investigation I stumbled upon the Select-Object cmdlet (“select” for short), which gives you the following command
$ Get-ChildItem -recurse | Select-Object FullName
But for some reason, this output is still formatted as a table and file paths that are too long are cut off… This is because Select-Object doesn’t actually select the property value, instead it basically wraps the selected property(-ies) in a new object and returns that – and naturally, the auto formatter kicks in. More research led me to the -ExpandProperty argument for Select-Object. The documentation has this to say about that argument:
“Specifies a property to select, and indicates that an attempt should be made to expand that property. Wildcards are permitted in the property name. For example, if the specified property is an array, each value of the array is included in the output. If the property contains an object, the properties of that object are displayed in the output.”
Ok… wait… what? I mean, I’m a developer and I barely understand what that means. No examples, nothing more. Not sure how that kind of documentation is useful, but maybe that’s just me. What it does is, it extracts the actual property value (that’s what I would have thought select should do natively). Now the command is
$ Get-ChildItem -recurse | Select-Object -ExpandProperty FullName
And that gives me the desired output which I can redirect into a file. And this is where my ambivalent feelings come from… why does a simple task like this have to be so damn hard?