It is not that no one could write this function. In fact, anyone could, but no one would! If someone did, everyone would use it – a function to add/update properties of an object.
More precisely, a function that
- Adds new properties
- Do appropriate checks to see property existence
- Ignore or alert if properties are missing
- Default $null values to given value
- Override properties with other values
This function is also uses a function that does set operations (about which I wrote before)
What are some use-cases?
Consider this as a more powerful Add-Member but with checks to see property existence etc.
- Add additional properties to objects before persisting (maybe to a database)
- Override certain property values before passing objects on to do other things
- Default $null valued properties to a sensible value
Although it is called Update-Object, it actually makes a copy of the object, updates the copy and returns that. Here it is:
<# .SYNOPSIS Updates an object by setting properties. Can add new properties .DESCRIPTION Given an object and set of properties to default or override (and optionally add), does so .INPUTS one or more objects with properties .OUTPUTS The results of the set operation .EXAMPLE $process = (Get-Process)[0] $processUpdateed = Update-Object ` -InputObject @($process) ` -OverrideColumns @{'UpdateUser' = $env:USERNAME; 'UpdateDate' = Get-Date;} ` -OverrideColumnsAddIfMissing: $true ` -OverrideColumnsIgnoreIfMissing:$true ` -DefaultColumns @{'TestProperty'='defaulted test'} ` -DefaultColumnsIgnoreIfMissing:$true ` -DefaultColumnsAddIfMissing:$true $processUpdateed | Select-Object UpdateUser, UpdateDate, TestProperty .NOTES Pretty self-explanatory. Depends on Get-SetOperationResult function. Version History v1.0 - 20151119 - Created by Jana Sattainathan | Twitter @SQLJana | WordPress: SQLJana.WordPress.com .LINK #> function Update-Object { [CmdletBinding()] param ( [Parameter(Mandatory=$true, Position=0)] [object[]]$InputObject, #Main options [Parameter(Mandatory=$false)] [HashTable]$DefaultColumns = @{}, [Parameter(Mandatory=$false)] [Switch]$DefaultColumnsIgnoreIfMissing = $false, [Parameter(Mandatory=$false)] [Switch]$DefaultColumnsAddIfMissing = $false, [Parameter(Mandatory=$false)] [HashTable]$OverrideColumns = @{}, [Parameter(Mandatory=$false)] [Switch]$OverrideColumnsIgnoreIfMissing = $false, [Parameter(Mandatory=$false)] [Switch]$OverrideColumnsAddIfMissing = $true ) BEGIN { [bool]$first = $false } PROCESS { [string] $fn = $MyInvocation.MyCommand [string[]] $inputColumns = @() #Validations #RCM: what if the first property is not reflective of all the properties? Unlikely, but... if ($first) { $first = $false #Add parameter validations if there are any... #Make sure that the object is not $null if ($InputObject -eq $null) { throw [System.ArgumentException] "[$fn]: Null value specified for argument `$InputObject!" } #Make sure that the input array has atleast one element if ($InputObject.Count -eq 0) { throw [System.ArgumentException] "[$fn]: No elements are in array argument `$InputObject!" } #Make sure that overlapping columns are not specified for defaulting and overriding! $overlappingColumns = Get-SetOperationResult ` -Left $DefaultColumns.Keys ` -Right $OverrideColumns.Keys ` -OperationType Intersection if ($overlappingColumns -ne $null) { throw [System.ArgumentException] "[$fn]: Same columns specified for both defaulting and override: [{0}]!" -f ($overlappingColumns -join ',') } } foreach($object in $InputObject) { [object]$result = $object | Select-object * #Get the list of input columns on the object $inputColumns = Get-Member -InputObject $result -MemberType Properties | Select-Object -Expand Name #---------------------------------- #Override the value if necessary #---------------------------------- foreach($overrideColumn in $OverrideColumns.Keys) { #If the column exists, override if ($inputColumns.Contains($overrideColumn)) { $result | %{$_.$overrideColumn = $OverrideColumns[$overrideColumn]} } else { #Ignore missing override column if we can if ($OverrideColumnsIgnoreIfMissing -eq $false) { #We cannot ignore that the override column is missing in the input object! throw [System.ArgumentException] "[$fn]: Expected override column [$overrideColumn] in `$InputObject!" } else { #Add the override column if asked to (and it is missing in the input object) if ($OverrideColumnsAddIfMissing -eq $true) { $result | Add-Member -MemberType NoteProperty -Name $overrideColumn -Value $OverrideColumns[$overrideColumn] } } } } #We CANNOT override and default. Override takes precedence. That is why we do not re-fetch the properties #$inputColumns = Get-Member -InputObject $object -MemberType Properties | Select-Object -Expand Name #---------------------------------- #Default the value if necessary #---------------------------------- foreach($defaultColumn in $DefaultColumns.Keys) { #If the column exists if ($inputColumns.Contains($defaultColumn)) { #If the value is null, default #Cannot do this if the value is null. Will get error "[214,40: Select-Object] Cannot process argument because the value of argument "obj" is null. Change the value of argument "obj" to a non-null value" #$value = $result | Select-Object -ExpandProperty $defaultColumn $value = $result | Foreach-Object { $_.$($defaultColumn)} if ($value -eq $null) { $result | %{$_.$defaultColumn = $DefaultColumns[$defaultColumn]} } } else { #Ignore missing override column if we can if ($DefaultColumnsIgnoreIfMissing -eq $false) { #We cannot ignore that the default column is missing in the input object! throw [System.ArgumentException] "[$fn]: Expected default column [$defaultColumn] in `$InputObject!" } else { #Add the override column if asked to (and it is missing in the input object) if ($DefaultColumnsAddIfMissing -eq $true) { $result | Add-Member -MemberType NoteProperty -Name $defaultColumn -Value $DefaultColumns[$defaultColumn] } } } } } Write-Output $result } END { } }
For example, let us manipulate the first Process object returned by the Get-Process function and add a couple of new properties to the object named UpdateUser and UpdateDate. The key thing is you could run this over and over and it would not error out and you do not have to do any additional checks:
$process = (Get-Process)[0] $processUpdated = Update-Object ` -InputObject @($process) ` -OverrideColumns @{'UpdateUser' = $env:USERNAME; 'UpdateDate' = Get-Date;} ` -OverrideColumnsAddIfMissing: $true ` -OverrideColumnsIgnoreIfMissing:$true ` -DefaultColumns @{'TestProperty'='defaulted test'} ` -DefaultColumnsIgnoreIfMissing:$true ` -DefaultColumnsAddIfMissing:$true $processUpdated | Select-Object UpdateUser, UpdateDate, TestProperty | ft -auto Returns: UpdateUser UpdateDate TestProperty ---------- ---------- ------------ 11/20/2015 7:34:41 AM defaulted test
Done! Just try to think how complicated code can become if you want to add properties on the fly and you have to check before adding (not to mention overriding, after checking for existence). Here, I am just trying to add a “ConnectionString” property to an object!
$result = $object #Check if ConnectionString property already exists (possibly due to refetching!) $resultColumns = Get-Member -InputObject $result -MemberType Properties | Select-Object -Expand Name if ($resultColumns.Contains('ConnectionString')) { $result.ConnectionString = $connectionString } else { $result | Add-Member -MemberType NoteProperty -Name ConnectionString -value $connectionString }
There are better ways to do this with PowerShell v5 but above code would work in older versions too. You may use the code as you see fit. Use at your own risk! Performance has not been tested. I would not use it work with more than a handful of objects!