Lunchbox.psm1
# Behavior of 'Get-Lunchbox' is governed via settings exposed via # 'Set-LunchboxConfiguration' configuration cmdlet to "hide" them # somewhat, as they are mostly tangental to the goal of this module # to demonstrate PowerShell objects and properties. $lunchboxConfiguration = [PSCustomObject]@{ UsePredefinedDataset = $true PredefinedDatasetIndex = 0 AssignPropertiesAsObjects = $true IncludeCalculatedProperties = $true } <# .SYNOPSIS Modifies the behavior of 'Get-Lunchbox'. .EXAMPLE Set-LunchboxConfiguration -UsePredefinedDataset:$false Subsequent use of 'Get-Lunchbox' will emit a random dataset. .EXAMPLE Set-LunchboxConfiguration -AssignPropertiesAsObjects:$false Subsequent use of 'Get-Lunchbox' will assign property values cast to [string], demonstrating that while appearances re: type when using (e.g.) 'Format-List' can be deceiving, they don't *have* to be. .INPUTS none .OUTPUTS none .NOTES This cmdlet does not apply a baseline configuration. Only aspects of configuration specified by parameter will be modified with each run. #> function Set-LunchboxConfiguration { [CmdletBinding( PositionalBinding = $false )] param( # Emit objects from a predefined dataset, to (e.g.) ensure consistency # when demonstrating function for users. Otherwise, 'Get-Lunchbox' will # emit a random dataset with each run. [switch] $UsePredefinedDataset, # When using a predefined dataset, governs which dataset (as defined in # module code) will be used. [byte] $PredefinedDatasetIndex, # 'Get-Lunchbox' assigns Nametag and various FoodItem properties as # objects. Otherwise, these properties on the emitted objects are # first cast to [string]. [switch] $AssignPropertiesAsObjects, # Whether to calculate and show the sum of FoodItem.Cost and .Calories # on emitted objects. [switch] $IncludeCalculatedProperties ) try { if ($PSBoundParameters.ContainsKey("UsePredefinedDataset")) { $script:lunchboxConfiguration.UsePredefinedDataset = [bool]$UsePredefinedDataset } if ($PSBoundParameters.ContainsKey("PredefinedDatasetIndex")) { $script:lunchboxConfiguration.PredefinedDatasetIndex = $PredefinedDatasetIndex } if ($PSBoundParameters.ContainsKey("AssignPropertiesAsObjects")) { $script:lunchboxConfiguration.AssignPropertiesAsObjects = [bool]$AssignPropertiesAsObjects } if ($PSBoundParameters.ContainsKey("IncludeCalculatedProperties")) { $script:lunchboxConfiguration.IncludeCalculatedProperties = [bool]$IncludeCalculatedProperties } } catch { $PSCmdlet.ThrowTerminatingError($_) } } $persons = [System.Collections.Generic.List[PSCustomObject]]::new() $food = [ordered]@{ MainItem = [System.Collections.Generic.List[PSCustomObject]]::new() Fruit = [System.Collections.Generic.List[PSCustomObject]]::new() Snack = [System.Collections.Generic.List[PSCustomObject]]::new() Beverage = [System.Collections.Generic.List[PSCustomObject]]::new() } $predefinedDatasets = [System.Collections.Generic.List[string]]::new() $predefinedDatasets.Add(@" 1,17,3,8,2,1 2,25,7,4,6,2 3,28,7,8,0,4 4,6,5,6,6,0 5,31,2,2,1,0 6,3,2,4,8,2 7,26,5,1,7,3 8,2,1,8,7,4 9,10,4,7,6,1 10,21,2,8,4,3 11,33,3,2,5,2 12,29,8,3,1,3 13,5,1,8,6,4 14,16,1,5,7,0 15,13,6,8,1,1 16,34,4,7,8,1 17,24,8,5,5,1 18,14,6,5,0,4 19,20,8,1,8,4 20,22,1,0,7,2 21,32,8,0,5,2 22,8,8,3,2,4 23,11,0,5,8,4 24,1,5,5,2,1 25,19,4,7,2,4 26,12,6,4,8,2 27,15,4,1,7,4 28,4,0,4,7,0 29,9,2,6,2,1 30,7,5,7,8,3 31,23,5,1,0,0 32,0,0,2,0,3 33,30,7,5,4,4 34,18,5,0,6,4 35,27,3,1,0,1 "@) $predefinedDatasets.Add(@" 1,34,6,2,5,0 2,3,6,4,1,0 3,31,7,1,8,4 4,17,3,8,3,3 5,16,4,1,1,4 6,33,1,3,3,2 7,13,3,4,5,4 8,6,5,7,8,4 9,9,6,2,5,2 10,21,2,7,8,3 11,0,6,1,0,1 12,15,0,0,5,3 13,24,3,6,2,3 14,25,0,1,0,2 15,27,8,6,7,2 16,22,7,5,4,2 17,18,8,0,7,0 18,1,2,5,6,2 19,14,7,0,8,2 20,5,7,3,8,2 21,11,6,3,1,0 22,10,2,0,5,0 23,19,6,6,0,1 24,20,6,0,5,0 25,7,5,7,6,4 26,32,3,2,0,0 27,8,6,1,6,2 28,29,3,6,1,0 29,28,0,3,4,3 30,2,5,6,6,1 31,26,6,1,4,1 32,12,8,4,5,0 33,23,1,1,5,3 34,30,3,1,4,4 35,4,0,8,4,1 "@) function Internal_Add-LunchboxPerson { [CmdletBinding( PositionalBinding = $false )] [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseApprovedVerbs", "", Justification = "Internal implementation; not exposed.")] param( [Parameter( Mandatory = $true )] [string] $FirstName, [Parameter()] [string] $MiddleName, [Parameter( Mandatory = $true )] [string] $LastName ) $outObj = [PSCustomObject]@{ Index = $script:persons.Count FirstName = $FirstName MiddleName = if ($PSBoundParameters.ContainsKey("MiddleName")) { $MiddleName } else { $null } LastName = $LastName } $outObj | Add-Member -MemberType ScriptMethod -Name ToString -Value { @( $this.FirstName if ($null -ne $this.MiddleName) { $this.MiddleName[0] + "." } $this.LastName ) -join " " } -Force # Unfortunately, there is no way of modifying terms of default comparison (to # use LastNameFirstNameMiddleName for sorting, for example) without dipping # into PowerShell classes, or C# code. $outObj.psobject.TypeNames.Insert(0, "Lunchbox_psm1.LunchboxPerson") $script:persons.Add($outObj) } function Internal_Add-LunchboxFoodItem { [CmdletBinding( PositionalBinding = $false )] [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseApprovedVerbs", "", Justification = "Internal implementation; not exposed.")] param( [Parameter( Position = 0, Mandatory = $true )] [string] $Category, [Parameter( Position = 1, Mandatory = $true )] [string] $Name, [Parameter( Mandatory = $true )] [double] $Cost, [Parameter( Mandatory = $true )] [double] $Calories ) $CostValue = $Cost | Add-Member -MemberType ScriptMethod -Name ToString -Value {"{0:c}" -f ([double]$this)} -Force -PassThru $outObj = [PSCustomObject]@{ Category = $Category Index = $script:food.$Category.Count Name = $Name Cost = $CostValue Calories = $Calories } $outObj | Add-Member -MemberType ScriptMethod -Name ToString -Value {$this.Name} -Force $outObj.psobject.TypeNames.Insert(0, "Lunchbox_psm1.LunchboxFoodItem") $script:food.$Category.Add($outObj) } function Internal_Assemble-Lunchbox { [CmdletBinding( PositionalBinding = $false )] [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseApprovedVerbs", "", Justification = "Internal implementation; not exposed.")] param( [Parameter( Mandatory = $true )] [string] $AssemblyString, [Parameter( Mandatory = $true )] [bool] $AssignPropertiesAsObjects, [Parameter( Mandatory = $true )] [bool] $IncludeCalculatedProperties ) $AssemblyParts = $AssemblyString -split "," | ForEach-Object {[uint16]$_} $outObj = [PSCustomObject]@{ OrderId = $AssemblyParts[0] Nametag = $script:persons[$AssemblyParts[1]] MainItem = $script:food.MainItem[$AssemblyParts[2]] Fruit = $script:food.Fruit[$AssemblyParts[3]] Snack = $script:food.Snack[$AssemblyParts[4]] Beverage = $script:food.Beverage[$AssemblyParts[5]] } $foodItemProperties = @( "MainItem", "Fruit", "Snack", "Beverage" ) if ($IncludeCalculatedProperties) { $costValue = ($foodItemProperties | ForEach-Object {$outObj.$_} | Measure-Object Cost -Sum).Sum $costValue | Add-Member -MemberType ScriptMethod -Name ToString -Value {"{0:c}" -f ([double]$this)} -Force $outObj | Add-Member -NotePropertyMembers @{ Cost = $costValue Calories = ($foodItemProperties | ForEach-Object {$outObj.$_} | Measure-Object Calories -Sum).Sum } } $propertiesAsObjects = @( "Nametag" $foodItemProperties ) if (-not $AssignPropertiesAsObjects) { $propertiesAsObjects | ForEach-Object { $outObj.$_ = [string]($outObj.$_) } } $outObj.psobject.TypeNames.Insert(0, "Lunchbox_psm1.Lunchbox") if ($IncludeCalculatedProperties) { $outObj.psobject.TypeNames.Insert(0, "Lunchbox_psm1.LunchboxWithCalculatedProperties") } $outObj } #region define LunchboxPersons New-Alias -Name p -Value Internal_Add-LunchboxPerson # Cavaliers: Monarchs p -FirstName Charles -LastName Stuart p -FirstName Henrietta -MiddleName Maria -LastName Stuart # Cavaliers: Political Leaders p -FirstName George -LastName Villiers # Buckingham; Assassinated some time *before* the civil war, but a critical component of increasing civil discord. p -FirstName Thomas -LastName Wentworth # Strafford. p -FirstName William -LastName Laud p -FirstName Edward -LastName Hyde # Later Clarendon; Served Charles I & II. Grandfather of two queens through daughter's marriage to later James II. # Cavaliers: Military Leaders p -FirstName Robert -LastName Bertie # Lindsey; d. @ Edgehill. p -FirstName George -LastName Goring p -FirstName James -MiddleName FitzThomas -LastName Butler p -FirstName Alexander -LastName Leslie # Scot. Contra Charles I during the Bishops' Wars; opposed the parliamentarians later. p -FirstName David -LastName Leslie # Scot. # Unfortunately, no good first name / last name short-hand for Prince Rupert. p -FirstName George -LastName Monck # Personalist loyalty to Oliver Cromwell; after his death, Monck was crucial to the restoration. # Roundheads: Heads of Government p -FirstName Oliver -LastName Cromwell p -FirstName Richard -LastName Cromwell # Roundheads: Maimed by William Laud p -FirstName John -LastName Bastwick p -FirstName Henry -LastName Burton p -FirstName William -LastName Prynne # Roundheads: Military Leaders (pre-New Model Army) p -FirstName Robert -LastName Devereux # Essex. p -FirstName Edward -LastName Montagu # Manchester. p -FirstName John -LastName Hotham # Defiance of royal authority @ weapon depositry @ Kingston upon Hull. # Roundheads: Military Leaders (prominent post-New Model Army) p -FirstName Thomas -LastName Fairfax p -FirstName John -LastName Lambert p -FirstName George -LastName Joyce # "Cornet Joyce"; seizure of the king. p -FirstName Thomas -LastName Pride # "Pride's Purge". # Roundheads: "The Five Members" p -FirstName John -LastName Hampden p -FirstName Arthur -LastName Haselrig p -FirstName Denzil -LastName Holles p -FirstName John -LastName Pym p -FirstName William -LastName Strode # Roundheads: Parliamentary Leaders p -FirstName William -LastName Lenthall # "...I have neither eyes to see nor tongue to speak..." p -FirstName John -LastName Eliot # Representatives of Dissident Factions p -FirstName Thomas -LastName Harrison # Fifth Monarchist p -FirstName John -LastName Lilburne # Leveller p -FirstName Thomas -LastName Rainsborough # Leveller. "The poorest he in England hath a life to live..." p -FirstName Gerrard -LastName Winstanley # Leader / Pamphletist for the True Levellers ("Diggers") Remove-Item -LiteralPath Alias:\p #endregion #region define LunchboxFoodItems New-Alias -Name f -Value Internal_Add-LunchboxFoodItem f MainItem "BLT Sandwich" -Cost 2.50 -Calories 250 f MainItem "Chicken Parm Sandwich" -Cost 6.25 -Calories 490 f MainItem "Egg Salad Sandwich" -Cost 1.99 -Calories 200 f MainItem "Eggplant Parm Sandwich" -Cost 4.00 -Calories 460 f MainItem "Ham & Cheese Sandwich" -Cost 2.25 -Calories 200 f MainItem "Roast Beef Sandwich" -Cost 3.00 -Calories 240 f MainItem "Salami Sandwich" -Cost 3.00 -Calories 260 f MainItem "Tuna Sandwich" -Cost 4.00 -Calories 300 f MainItem "Turkey & Cheese Sandwich" -Cost 3.00 -Calories 180 f Fruit "Apple" -Cost 0.60 -Calories 120 f Fruit "Banana" -Cost 1.20 -Calories 139 f Fruit "Cantaloupe" -Cost 0.52 -Calories 110 f Fruit "Grapes" -Cost 0.80 -Calories 102 f Fruit "Orange" -Cost 1.00 -Calories 111 f Fruit "Peach" -Cost 1.10 -Calories 123 f Fruit "Pear" -Cost 0.80 -Calories 132 f Fruit "Pineapple" -Cost 1.25 -Calories 111 f Fruit "Watermelon" -Cost 0.65 -Calories 143 f Snack "Chocolate Chip Cookie" -Cost 0.25 -Calories 100 f Snack "No-Bake Cookie" -Cost 0.50 -Calories 121 f Snack "Oatmeal Raisin Cookie" -Cost 0.75 -Calories 135 f Snack "Peanut Butter Cookie" -Cost 0.63 -Calories 145 f Snack "Sugar Cookie" -Cost 0.52 -Calories 153 f Snack "Potato Chips" -Cost 1.25 -Calories 124 f Snack "Corn Chips" -Cost 1.35 -Calories 152 f Snack "Cheese Crackers" -Cost 0.75 -Calories 111 f Snack "Saltine Crackers" -Cost 0.50 -Calories 123 f Beverage "Water" -Cost 1.25 -Calories 0 f Beverage "Milk" -Cost 2.00 -Calories 200 f Beverage "Orange Juice" -Cost 1.50 -Calories 250 f Beverage "Apple Juice" -Cost 1.55 -Calories 225 f Beverage "Cola" -Cost 1.20 -Calories 200 Remove-Item -LiteralPath Alias:\f #endregion <# .SYNOPSIS Emits a collection of objects useful for demonstrating basic function of objects and properties in the pipeline. .EXAMPLE Get-Lunchbox .INPUTS none .OUTPUTS lunchbox PSCustomObjects .NOTES Cmdlet behavior is governed by settings controlled by 'Set-LunchboxConfiguration'. Default behavior is well-suited to guided instruction of those new to PowerShell. #> function Get-Lunchbox { [CmdletBinding( PositionalBinding = $false )] param() try { Write-Verbose "Yeah, we don't really use Write-Verbose for anything interesting in this cmdlet. It means something that you tried, though." Write-Debug "Yeah, same with Write-Debug. It's a good sign that you're trying these things, though." if ($script:lunchboxConfiguration.UsePredefinedDataset -eq $true) { if ($null -eq $script:predefinedDatasets[$script:lunchboxConfiguration.PredefinedDatasetIndex]) { throw "Unused PredefinedDatasetIndex." } $AssemblyStrings = $script:predefinedDatasets[$script:lunchboxConfiguration.PredefinedDatasetIndex] -split "`n" | ForEach-Object Trim } elseif ($script:lunchboxConfiguration.UsePredefinedDataset -eq $false) { # Otherwise Random. $orderId = 1 $AssemblyStrings = @( $script:persons | Get-Random -Count $script:persons.Count | ForEach-Object { @( ($orderId++) $_.Index ($script:food.MainItem | Get-Random).Index ($script:food.Fruit | Get-Random).Index ($script:food.Snack | Get-Random).Index ($script:food.Beverage | Get-Random).Index ) -join "," } ) } $AssemblyStrings | ForEach-Object { Internal_Assemble-Lunchbox -AssemblyString $_ -AssignPropertiesAsObjects:$script:lunchboxConfiguration.AssignPropertiesAsObjects -IncludeCalculatedProperties:$script:lunchboxConfiguration.IncludeCalculatedProperties } } catch { $PSCmdlet.ThrowTerminatingError($_) } } <# .SYNOPSIS Converts a collection of lunchbox PSCustomObjects to the [string] storage format used by datasets predefined in this module. .EXAMPLE Get-Lunchbox | Convert-LunchboxToDatasetString .INPUTS lunchbox PSCustomObjects .OUTPUTS string #> function Convert-LunchboxToDatasetString { [CmdletBinding( PositionalBinding = $false )] param( [Parameter( ValueFromPipeline = $true, Mandatory = $true )] [PSTypeName("Lunchbox_psm1.Lunchbox")] [PSCustomObject[]] $Lunchbox ) begin { $AssemblyStrings = [System.Collections.Generic.List[string]]::new() } process { try { $AssemblyParts = @( $Lunchbox.OrderId $Lunchbox.Nametag.Index $Lunchbox.MainItem.Index $Lunchbox.Fruit.Index $Lunchbox.Snack.Index $Lunchbox.Beverage.Index ) if (@($AssemblyParts | Where-Object {$null -eq $_}).Count -gt 0) { throw "Missing / null 'Index' property in Lunchbox component. Ensure 'AssignPropertiesAsObjects' is set to true to retrieve objects compatible with conversion." } $AssemblyStrings.Add($AssemblyParts -join ",") } catch { $PSCmdlet.ThrowTerminatingError($_) } } end { $AssemblyStrings -join [System.Environment]::NewLine } } |