From 1bb9e9d4f3db3eb20536b291cc97312ee32191e2 Mon Sep 17 00:00:00 2001 From: Chris Dent Date: Sun, 15 Sep 2019 18:21:21 +0100 Subject: [PATCH] Updates AboutSelectObject (#247) * Adds new examples * Fixes single line comments * Fixes comments. Updates PSCustomObject note. * Review fixes * Fixes syntax * Fixes placeholder size --- .../Cmdlets 1/AboutSelectObject.Koans.ps1 | 285 ++++++++++++++++-- 1 file changed, 256 insertions(+), 29 deletions(-) diff --git a/PSKoans/Koans/Cmdlets 1/AboutSelectObject.Koans.ps1 b/PSKoans/Koans/Cmdlets 1/AboutSelectObject.Koans.ps1 index 491ac2670..ae30b6015 100644 --- a/PSKoans/Koans/Cmdlets 1/AboutSelectObject.Koans.ps1 +++ b/PSKoans/Koans/Cmdlets 1/AboutSelectObject.Koans.ps1 @@ -4,69 +4,296 @@ param() <# Select-Object - Select-Object is a utility cmdlet that is used to 'trim' objects down to just - the selected properties. It is particularly useful to get custom displays - of data, and is capable of adding new properties as well. + Select-Object is a utility cmdlet which serves a number of different purposes. + + * Select or skip a fixed number of objects from a pipeline + * Select unique objects from an array or collection + * Select individual properties from an object + * Create a new object with additional, custom, properties #> Describe 'Select-Object' { + It 'can select specific properties from an object' { + <# + Select-Object can accept an array of property names and creates a + new custom object using those properties. + #> - It 'selects specific properties of an object' { - $File = New-TemporaryFile - "Hello" | Set-Content -Path $File.FullName - $File = Get-Item -Path $File.FullName + $Selected = Get-Proces -Id $PID | Select-Object Name, ID, Path - $Selected = $File | Select-Object -Property Name, Length - $Selected.PSObject.Properties.Name | Should -Be @('Name', '__') + @('____', '____', 'Path') | Should -Be $Selected.PSObject.Properties.Name - __ | Should -Be $Selected.Length + $Selected.____ | Should -Be $PID } It 'can exclude specific properties from an object' { - $File = New-TemporaryFile + <# + Individual properties can be excluded from a selection. + + Both the Property and ExcludeProperty parameters support wildcards. + #> + + $Folder = Get-Item -Path $PSHome - '__' | Should -Be $File.Attributes + '____' | Should -Be $Folder.Attributes - $Object = $File | Select-Object -Property * -ExcludeProperty Attributes, Length + $Selected = $Folder | Select-Object -Property * -ExcludeProperty Attributes - $Object.Attributes | Should -Be $null - __ | Should -Be $Object.Length + $Selected.Attributes | Should -BeNullOrEmpty } It 'changes the object type' { - $FileObject = Get-Item -Path $home + <# + When properties are selected from an object a new custom object is created. + + The new object is also tagged with a PSTypeName derived from the original type name. + #> + + $Folder = Get-Item -Path $PSHome - $FileObject | Should -BeOfType __ + $Selected.GetType().FullName | Should -BeOfType [System.IO.Directoryinfo] - $Object = $FileObject | Select-Object -Property FullName, Name, DirectoryName - $Object | Should -BeOfType __ + 'System.IO.Directoryinfo' | Should -BeIn $Folder.PSTypeNames + + # The new selected object is an instance of [System.Management.Automation.PSCustomObject]. + + $Selected = $Folder | Select-Object -Property * -ExcludeProperty Attributes + + $Selected | Should -BeOfType [System.Management.Automation.PSCustomObject] + + '____' | Should -BeIn $Selected.PSTypeNames } It 'can retrieve just the contents or value of a property' { - $FileObject = Get-Item -Path $home + # Individual properties can be expanded, retrieving the just a value. + + $PropertyToExpand = '____' - $FileName = $FileObject | Select-Object -ExpandProperty __ -ErrorAction SilentlyContinue + $Value = Get-Item -Path $PSHome | Select-Object -ExpandProperty $PropertyToExpand - $FileName | Should -Be $FileObject.Name + $Value | Should -Be 'Directory' + } + + It 'can merge the properties of a nested object with properties from the parent' { + <# + The ExpandProperty parameter can be used to "move" the properties of a nested + property up to a new parent object. + #> + + $PowerShellExe = Get-Process -Id $PID | Select-Object -ExpandProperty Path | Get-Item + + $Selected = $PowerShellExe | Select-Object Name -ExpandProperty VersionInfo + + # The resulting object will contain all of the properties found under VersionInfo. + + $Selected.____ | Should -Be $PowerShellExe.FullName } It 'can pick specific numbers of objects' { - $Array = 1..100 + $Array = 1..100 -as [string[]] $FirstThreeValues = $Array | Select-Object -First 3 - __ | Should -Be $FirstThreeValues + @('__', '__', '__') | Should -Be $FirstThreeValues $LastFourValues = $Array | Select-Object -Last 4 - __ | Should -Be $LastFourValues + @('__', '__', '__', '__') | Should -Be $LastFourValues $Values = $Array | Select-Object -Skip 10 -First 5 - __ | Should -Be $Values + @('__', '__', '__', '__', '__') | Should -Be $Values + + # SkipLast cannot be used alongside the Last, First, and Skip parameters. + + $Values = $Array | Select-Object -SkipLast 95 + @('__', '__', '__', '__', '__') | Should -Be $Values } It 'can ignore duplicate objects' { - $Array = 6, 1, 4, 8, 7, 5, 3, 9, 2, 3, 2, 1, 5, 1, 6, 2, 8, 4, - 7, 3, 1, 2, 6, 3, 7, 1, 4, 5, 2, 1, 3, 6, 2, 5, 1, 4 + # Select-Object can be used to create a unique list. + + $Array = '6', '1', '4', '8', '7', '5', '3', '9', '2', '3', '2', '1', '5', '1', '6', + '2', '8', '4', '7', '3', '1', '2', '6', '3', '7', '1', '4', '5', '2', '1', '3', + '6', '2', '5', '1', '4' $UniqueItems = $Array | Select-Object -Unique - 6, '__', 4, 8, '__', '__', 3, '__', 2 | Should -Be $UniqueItems + @('6', '__', '4', '8', '__', '__', '3', '__', '2') | Should -Be $UniqueItems + } + + It 'can ignore duplicate complex objects' { + <# + Unique works on some more complex objects. + + Ideally objects should be directly comparable, although this is + rare outside of Strings, numeric types, enumeration types, and other value types. + + Select-Object falls back on the ToString method of an object. + #> + + $Processes = @( + Get-Process -Id $PID + Get-Process -Id $PID + ) + + __ | Should -Be $Processes.Count + + $UniqueProcesses = $processes | Select-Object -Unique + + __ | Should -Be $UniqueProcesses.Count + + <# + As Process objects are not directly comparable, they are made unique by comparing a + string representation of the process. + + The string to compare is created as shown below: + + $object = Get-Process -Id $PID + [PSObject]::AsPSObject($object).ToString() + + For PSCustomObjects the conversion to string is slightly different: + + $object = [PSCustomObject]@{ Name = 'one'} + [PSObject]::AsPSObject($object.PSBase).ToString() + + This value includes the name of the process, but not the + ProcessId. + + If several processes of the name name are running these + will be removed by the Unique parameter. + + If the Property parameter is used, Unique is evaluated after creating a new + custom object. + #> + } + + It 'maintains the original object type when the Property parameter is not used' { + # When the Property parameter is used, a new object + + $Files = Get-ChildItem -Path $PSHome -File + + $SelectedFile = $Files | Select-Object -First 1 + + '____' | Should -Be $SelectedFile.GetType().FullName + } + + It 'supports custom, or calculated, properties' { + <# + The property parameter can read a hashtable which describes a property. + + The hashtable requires a Name, and Expression to calculate a value for the property. + The Expression is most often a script block, a short script enclosed in curly braces. + + The variable $_, or $PSItem, is used to refer to the object from the pipeline within the + Exrpression. Properties may be used even if they are not selected. + #> + + $Selected = Get-Process -Id $PID | Select-Object @( + 'Name' + 'Id' + @{ Name = 'RunningTime'; Expression = { (Get-Date) - $_.StartTime } } + ) + + $Selected.____ | Should -BeGreaterThan 0 + + <# + Custom properties are often used to merge information from multiple sources into + a single object. + #> + + $Selected = Get-Process -Id $PID | Select-Object @( + 'Name' + 'Id' + @{ Name = 'RunningTime'; Expression = { (Get-Date) - $_.StartTime } } + @{ Name = 'Size'; Expression = { (Get-Item $_.Path).Length } } + ) + } + + It 'does not need a script block when renaming a property' { + <# + When renaming a property, a string with the existing property name can be used in place of the + script block. + #> + + $Process = Get-Process -Id $PID + $Selected = $Process | Select-Object @( + 'Name' + @{ Name = 'ProcessId'; Expression = 'Id' } + ) + + $Selected.____ | Should -Be $Process.Id + } + + It 'allows Label to be used instead of Name' { + $Process = Get-Process -Id $PID + $Selected = $Process | Select-Object @( + 'Name' + @{ Label = 'ProcessId'; Expression = 'Id' } + ) + + $Selected.____ | Should -Be $Process.Id + } + + It 'supports abbreviated names in a calculated property' { + <# + Select-Object supports shorthand for the keys in the hashtable. + + * Name can be abbreviated to n + * Label can be abbreviated to l + * Expression can be abbreviated to e + + Properties are calculated when Select-Object runs, they are not updated or recalculated later. + #> + + $Selected = Get-Process -Id $PID | Select-Object @( + 'Name' + 'Id' + @{ n = 'Size'; e = { (Get-Item $_.Path).Length } } + ) + + $Selected.____ | Should -BeOfType [TimeSpan] + } + + It 'From Select-Object to PSCustomObject' { + <# + As mentioned above, the Select-Object command creates instances of the + System.Management.Automation.PSCustomObject type. + + Select-Object can be used to create an object from scratch. The + technique below was widely used before PowerShell 2 was released. + #> + + $customObject = '' | Select-Object -Property @( + @{ Name = 'Property1'; Expression = { 'Value1' } } + @{ Name = 'Property2'; Expression = { 'Value2' } } + ) + + $customObject | Should -BeOfType [System.Management.Automation.PSCustomObject] + + # The approach above was replaced with New-Object with the release of PowerShell 2. + + $customObject = New-Object PSObject -Property @{ + Property1 = 'Value1' + Property2 = 'Value2' + } + + $customObject | Should -BeOfType [System.Management.Automation.PSCustomObject] + + <# + The [PSCustomObject] type accelerator was made available with PowerShell 3. It provides + a neater way of creating objects from scratch, replacing both of the methods above. + + Select-Object is still widely used when creating a new object from another object. + #> + + $customObject = [PSCustomObject]@{ + Property1 = 'Value1' + Property2 = 'Value2' + } + + $customObject | Should -BeOfType [System.Management.Automation.PSCustomObject] + + <# + Despite the similar naming, the PSCustomObject type accelerator is shorthand for + System.Management.Automation.PSObject. It is named as it is because it creates a custom object. + #> + + [PSCustomObject] | Should -Be [System.Management.Automation.PSObject] } }