4

I found a PowerShell script that I thought I could adapt to my purposes.

It contains this line that formats the output:

$largeSizefiles = get-ChildItem -path $filesLocation -include $Extension -recurse -ErrorAction "SilentlyContinue" | ? { $_.GetType().Name -eq "FileInfo" } | where-Object {$_.Length -gt $fileSize} | sort-Object -property length  | Select-Object Name, @{Name="Size In KB";Expression={ "{0:N0}" -f ($_.Length / 1KB)}},@{Name="LastWriteTime";Expression={$_.LastWriteTime}},@{Name="Path";Expression={$_.directory}} -first $filesLimit

The key part seems to be this:

Select-Object Name, @{Name="Size In KB";Expression={ "{0:N0}" -f ($_.Length / 1KB)}},@{Name="LastWriteTime";Expression={$_.LastWriteTime}},@{Name="Path";Expression={$_.directory}} -first $filesLimit

I have read the ss64.com tutorial on Select-Object, but I don't find anything to explain how the expressions of the form @{.....} are formatting the text.

The ss64.com page on the @ operator shows it in the format of @( ... ), with parens, not braces.

The code above results in the following output:

Name          : RPI-Image-1-Copy.img
Size In MB    : 29,477
Path          : D:\VirtualDriveShare
LastWriteTime : 8/18/2015 6:27:51 PM

I'm familiar with a number of programming languages, but this is non-obvious to me, and I haven't found any clear explanation online. Can anyone point me to a good tutorial?

tim11g
  • 425
  • 5
  • 9
  • 21
  • 1
    Does this help https://technet.microsoft.com/en-us/library/ff394367.aspx – Mass Nerder Oct 19 '16 at 18:02
  • That is interesting, but doesn't explain the syntax of Select-Object with a comma-delimited list of parameters. I'm trying to simplify to identify what the components mean. Using this command "| Select-Object Name -first $filesLimit" I get this output: "Name : projectx.zip". Using this command " Select-Object @{Name="Path";Expression={$_.directory}} -first $filesLimit" I get "Path : D:\Download\Software\EDA" Using both together, separated by comma, I get both output strings on separate lines. Why? Where is the linefeed coming from? – tim11g Oct 19 '16 at 18:50
  • Why is one a simple "Name", and the other a complex @{} expression? – tim11g Oct 19 '16 at 18:56
  • Read [about_Object_Creation](https://technet.microsoft.com/en-us/library/jj159398.aspx). **Why is one a simple "Name", and the other a complex @{} expression?** Because simple `Name` is the same as `@{Name="Name";Expression={$_.Name}}` (one could see superabundance of such syntax construct). For deep understanding, think about `$largeSizefiles.GetType() | fl *` (`Object[]` array) and `$largeSizefiles[0].GetType() | fl *` (`PSCustomObject`). – JosefZ Oct 19 '16 at 20:49
  • I think for some reason you're seeing the output via Format-List rather than Format-Table. Try `$largeSizefiles | Format-List` vs `$largeSizefiles | Format-Table` – Christopher_G_Lewis Oct 19 '16 at 21:07
  • The default `Write-Output` for items left on the pipeline includes a newline between items. If this is one object being passed through, then `Select`ed into its constituent pieces, it'll pick up the individual items from the pipeline and insert a newline. – AdmBorkBork Oct 20 '16 at 01:48

4 Answers4

7

Select-Object can use a hash table of Header/Values for each item. In this example:

Select-Object Name, @{Name="Size In KB";Expression={ "{0:N0}" -f ($_.Length / 1KB)}} ...

the script is selecting Name, then "Name in KB" that is derived from the current pipeline object's Length parameter.

This is further formatted by first dividing it by 1024 then using {0:N0} to display it.

Powershell uses the .Net string format syntax for display - in this case {0:N0} translates into:

// N or n (Number): It represent how many decimal places of zeros to show.
String.Format("{0:N4}", pos);      //”10.0000″

You might want to take a look at Kathy Kam's Format 101 and Format 102 articles:

for further details on string formatting.

Christopher_G_Lewis
  • 3,647
  • 21
  • 27
2

What you're missing is that @{...} indicates a hashtable array, which are composed of key-value pairs.

As pointed out in the comments, there's a Technet article on this very thing. In your example, what's happening is the name/title is being assigned to the key in the hashtable, and a variable with a formatting expression is being assigned to the value in the hashtable.

Hashtables in PowerShell are fairly straightforward, but in case it's helpful, ss64's page is here, and Technet has a tutorial page as well.

HopelessN00b
  • 53,385
  • 32
  • 133
  • 208
2

I agree with all other answers, the hash-table being a calculated property in this case. I find these one liners wonderful, but they could be presented in a far better way. Technical still a one-liner but readability on a different scale.

$largeSizefiles = get-ChildItem `
    -path $filesLocation `
    -include $Extension `
    -recurse `
    -ErrorAction "SilentlyContinue" | 
  Where-object { ($_.GetType().Name -eq "FileInfo") `
            -and ($_.Length -gt $fileSize)} | 
  sort-Object -property length  | 
  Select-Object Name, 
    @{Name="Size In KB";Expression={ "{0:N0}" -f ($_.Length / 1KB)}}, 
    @{Name="LastWriteTime";Expression={$_.LastWriteTime}}, 
    @{Name="Path";Expression={$_.directory}} -first $filesLimit

If PoSh expects a continuation (after a , or a |) you can simply insert a newline or of course after the line continuation char the backtic ` .

LotPings
  • 1,015
  • 7
  • 12
2

@{...=...; ...=...} is a hashtable. You may know of them as "dictionaries" in other languages. These are key/value pairs. Semicolons (;) separate the different pairs, and the key and value are separated by an equal sign (=). Note that the key can have implied quotes, making it a string.

The { "{0:N0}" -f ($_.Length / 1KB) } part is a script block. There are a bunch these; all the Expression keys in the hashtables map to one. You can tell this by the curly braces without a prefix surrounding it. These are essentially anonymous functions (lambdas). The $_ is the current item from the pipeline, so when this script block is executed, it expands to a single item from the pipeline input.

The particular script block "{0:N0}" -f ($_.Length / 1KB) part is just using the -f operator to do formatting. This is equivalent to .NET's String.Format, where the pattern string is on the left and the arguments for the pattern are an array of objects on the right. The 0 is just the argument index (so first argument) and the :N0 tells it to format as a number with 0 decimal places.

Now between all the hashtables, we have a bunch of comma. These make an array! It turns out that the surrounding @(...) part is optional.

So we have an array containing a few strings and a bunch of hashtables with the keys Name and Expression, and that array is an argument being passed into Select-Object. Namely, it's the -Property argument. Note this line from SS64's doc:

To add a calculated property to an object, specify a hash table as a value of the -Property parameter. The hash table must include two keys: Name and Expression with the Expression key assigned a script block that will determine the value of the property.

So this all makes a little more sense now. It's just Select-Object transforming the input, and it's putting together a bunch of "calculated" (determined by script block) attributes and one "uncalculated" (already present on the incoming object) attribute for the output object.

A minor note: I believe this particular calculated attribute is somewhat silly:

@{Name="LastWriteTime";Expression={$_.LastWriteTime}}

I don't see any reason it couldn't have just been selected directly like Name was.

jpmc26
  • 141
  • 1
  • 8