Tuesday, July 21, 2015

Beginner's Guide - Working with lists (part 4)

In this final installment of my 4 part series on working with lists, we'll cover advanced Select-Object statements, utilizing the power of computed expressions with custom names for each column (parameter).

In case you missed the first three parts, you can check them out here:

Part 1 - simple string arrays
Part 2 - object arrays
Part 3 - building custom object arrays

Custom Parameter Names

From Part 2 - Selecting, we covered how to return only a subset of columns (parameters) from our object arrays and we can even order the parameters so they appear the way we want them in a table or CSV file, etc.  Custom parameter names takes this one step further and instead of just selecting which parameters we want to output, we also choose new names for them.

To do this, we use hashtables, one for each parameter.  Within the hashtable, we specify two name-value pairs.  The first one is "name" (or "n" is also acceptable) with the value equal to whatever name we want to give the new parameter.  The second pair is "expression" (or "exp", or even just "e") with the value of the old parameter's name in the form of a powershell expression.

Below you see an example where we replace the default "dir" output with our own names.  Don't be afraid of the dollar-underscore part of the statement, we'll cover this later; for now, just understand that it's a special variable that represents the current object (record) in the table.  The select statement transforms the incoming data object by object and the $_ variable just means the current one it's processing.

PS C:\> dir

    Directory: C:\

Mode                LastWriteTime         Length Name
----                -------------         ------ ----
d-r---        7/19/2015   3:39 AM                Program Files
d-r---        7/21/2015  12:49 PM                Program Files (x86)
d-r---        7/19/2015   3:39 AM                Users
d-----        7/19/2015   3:40 AM                Windows
-a----         7/2/2015   7:39 PM           2544 myserverstatus.csv
-a----         7/2/2015   7:39 PM           2544 myserverstatus.xml

PS C:\> dir |select Name

Program Files
Program Files (x86)

PS C:\> dir | select @{n="Nombre"; e={$_.name}}

Program Files
Program Files (x86)

PS C:\> dir | select @{n="Nombre"; e={$_.name}}, @{n="Type"; e={$_.mode}}

Nombre              Type
------              ----
Program Files       d-r---
Program Files (x86) d-r---
Users               d-r---
Windows             d-----
myserverstatus.csv  -a----
myserverstatus.xml  -a----

PS C:\> dir | select @{n="Nombre"; e={$_.name}}, @{n="Type"; e={$_.mode}}, @{n="File Write Time"; e={$_.LastWriteTime}}

Nombre              Type   File Write Time
------              ----   ---------------
AMD                 d----- 7/19/2015 3:38:16 AM
EFI                 d----- 5/23/2015 4:52:28 AM
Games               d----- 6/20/2015 8:13:11 PM
HyperV              d----- 7/7/2015 3:58:26 PM
Intel               d----- 6/24/2015 6:59:47 PM
MSI                 d----- 6/24/2015 6:50:19 PM
PerfLogs            d----- 7/10/2015 4:04:22 AM
Program Files       d-r--- 7/19/2015 3:39:38 AM
Program Files (x86) d-r--- 7/21/2015 12:49:26 PM
Users               d-r--- 7/19/2015 3:39:41 AM
Windows             d----- 7/19/2015 3:40:35 AM
Windows.old         d----- 7/19/2015 4:36:26 AM
myserverstatus.csv  -a---- 7/2/2015 7:39:18 PM
myserverstatus.xml  -a---- 7/2/2015 7:39:27 PM

PS C:\>

As you can see the expression part of the hashtable has it's own curly brackets.  You can use this template to help you create your own custom parameter names:

$datatable | select @{n="New Parameter Name 1"; e={$_.OldName1}}, @{n="New Parameter Name 2"; e={$_.OldName2}}, @{n="New Parameter Name X"; e={$_.OldNameX}}

Rounding and changing units

Another very common use for using custom selects is to change data units and rounding fractions.  Using another "dir" example, say we want to convert the units to GB or TB, as working with bytes can be annoying.

PS C:\> dir C:\HyperV\VHD\ | select Name, Length

Name                  Length
----                  ------
BVadm1.vhdx      11446255616
BVDC1.vhdx       10305404928
BVServer10.vhdx   9030336512
BVSMA1.vhdx       9936306176
BVSQL1.vhdx      10741612544
BVSQL1_DATA.vhdx  1077936128
DBadm2.vhdx      18291359744
Dev10.vhdx       18996002816

PS C:\> dir C:\HyperV\VHD\ | select Name, @{n="Size (GB)"; e={$_.length / 1GB}}

Name               Size (GB)
----               ---------
BVadm1.vhdx      10.66015625
BVDC1.vhdx        9.59765625
BVServer10.vhdx   8.41015625
BVSMA1.vhdx       9.25390625
BVSQL1.vhdx      10.00390625
BVSQL1_DATA.vhdx  1.00390625
DBadm2.vhdx      17.03515625
Dev10.vhdx       17.69140625

PS C:\> dir C:\HyperV\VHD\ | select Name, @{n="Size (GB)"; e={[math]::round($_.length / 1GB,2)}}

Name             Size (GB)
----             ---------
BVadm1.vhdx          10.66
BVDC1.vhdx             9.6
BVServer10.vhdx       8.41
BVSMA1.vhdx           9.25
BVSQL1.vhdx             10
BVSQL1_DATA.vhdx         1
DBadm2.vhdx          17.04
Dev10.vhdx           17.69

PS C:\>
Notice how I can mix and match normal parameters with custom parameters, and that they appear in the order I list them in the select statement.

Computing new parameters

Perhaps the most powerful use of custom selects is to compute data from other parameters.  When possible, using custom selects is the fastest way to transform and compute data from lists. It's much better than writing a whole script to create a new object array and build new objects with different parameters.
PS C:\> dir | select Name,
>>> @{n="Directory"; e={$_.mode -like "d*"}},
>>> @{n="Name Length"; e={$_.name.length}},
>>> @{n="Days Old"; e={((get-date) - $_.creationTime).days}}

Name                Directory Name Length Days Old
----                --------- ----------- --------
AMD                      True           3       31
HyperV                   True           6       31
Intel                    True           5       27
Program Files            True          13       11
Program Files (x86)      True          19       11
Users                    True           5       11
Windows                  True           7       11
Windows.old              True          11        2
myserverstatus.csv      False          18       19
myserverstatus.xml      False          18       19

PS C:\> gwmi win32_logicaldisk | select @{n="Letter"; e={$_.deviceid[0]}}, 
>>> @{n="Used Space (TB)"; e={[math]::round(($_.size - $_.freespace)/1TB,3)}},
>>> @{n="Percent Free"; e={[math]::round(100 * $_.freespace / $_.size,0)}}

Letter Used Space (TB) Percent Free
------ --------------- ------------
     C           0.022           82
     D           0.017           86
     I           2.202           27
     M           3.917           22

PS C:\>
As you can see, using custom selects with object arrays is a great way to extract exactly what you need from source data without doing too much work. Also note that you can put each parameter definition on a new line as long as you end the current line with a comma as I did here.

No comments: