I have:
$method = "property.childproperty"
How can I get to $foo = $object.property.childproperty by using the $method variable?
One way to do it, split on . your string in $method then loop through each substring assigning, get the property value via dot-notation and assign it to a temporary variable:
$object = [pscustomobject]@{ property = [pscustomobject]@{ childproperty = 'hi' } }
$method = 'property.childproperty'
$tmp = $object
$method.Split('.') | ForEach-Object { $tmp = $tmp.$_ }
$tmp # hi
If you need to perform some validation, checking if such property exist, you can use .PSObject.Properties instead:
$object = [pscustomobject]@{ property = [pscustomobject]@{ childproperty = 'hi' } }
$method = 'property.notexist'
$tmp = $object
$method.Split('.') | ForEach-Object {
if ($prop = $tmp.PSObject.Properties[$_]) {
$tmp = $prop.Value
return
}
throw "Property '$_' not found on '$tmp'...."
}
# Property 'notexist' not found on '@{childproperty=hi}'....
Another way to do it, could be via Invoke-Expression or ScriptBlock.Create. However, you should note that both approaches are unsafe if $method comes from an untrusted source.
Here is how it'd look:
$object = [pscustomobject]@{ property = [pscustomobject]@{ childproperty = 'hi' } }
$method = 'property.childproperty'
& ([scriptblock]::Create("`$object.$method")) # hi
Invoke-Expression "`$object.$method" # hi
And here is why it could be dangerous if $method was from an arbitrary source:
$object = [pscustomobject]@{ property = [pscustomobject]@{ childproperty = 'hi' } }
$method = 'foo; Get-ChildItem'
& ([scriptblock]::Create("`$object.$method"))
Invoke-Expression "`$object.$method"
Split the $method string into its constituent parts, then simply dereference the "next" step in the chain until you've reached the last one:
# sample input data
$foo = @{ property = @{ childproperty = 'value' }}
$method = 'property.childproperty'
# split the path into individual property names
$steps = $method -split '\.'
# $cursor will refer to the value resolved in the previous step, starting with the target object $foo
$cursor = $foo
# keep resolving until there are no more steps/property names in the chain
while ($steps) {
$next,$steps = $steps
# overwrite previous value reference with whatever the next step resolves to
$cursor = $cursor.$next
}
Write-Host "Resolved value is `$cursor`"
This pattern is easily abstracted into a re-usable function
Your question contains a very specific example where the solution has likely a lot of sub-questions, possible (code injection) pitfalls, and additional requirements. For this, I created a ObjectGraphTools module which has cmdlets available that might prevent you from these issues and reinventing the wheel.
To give you an idea of the features of the included Get-Node cmdlet:
Install-Module -Name ObjectGraphTools
$foo = @{ property = @{ childproperty = 42 }}
$method = 'property.childproperty'
$foo | Get-Node $Method -Value
42
But due to the PowerShell member-access enumeration feature, your concerned property might not always be where you expect it to be, e.g.:
$foo = @{ property = @{ id = 7 }, @{ childproperty = 42 } }
$foo.property.childproperty
42
In which case you can't set it the same way you retrieve it:
$foo.property.childproperty = 421
InvalidOperation: The property 'childproperty' cannot be found on this object. Verify that the property exists and can be set.
Because the path to the concerned node is actually here:
$Node = $foo | Get-Node $Method
$Node
Path Name Depth Value
---- ---- ----- -----
property[0].childproperty childproperty 3 42
So, if you want to set the property, you need to use property[0].childproperty or do it via the found $Node:
$Node.Value = 421
$foo | ConvertTo-Expression -Expand 0
@{ property = @(@{ id = 7 }, @{ childproperty = 421 }) }
And in case one of the properties in the path contains a special character as e.g. a space, you might apply the common PowerShell quoting rules, like:
$foo = @{ property = @{ id = 7 }, @{ 'child property' = 42 } }
$foo | Get-Node property.'child property'
Path Name Depth Value
---- ---- ----- -----
property[1].'child property' child property 3 42
Anyways, together with the Extended Dot Notation (XDN) you might do things along with targeting deep descendants, use wildcard, select parents etc.
E.g.: you need the ID value of the sibling of the property that starts with child and has the value 42:
$foo | Get-Node ~child*=42...id -Value
7
Explanation:
~: find any descendent propertychild*: whare the name starts with child=42: and has a value equal to 42...: get its grandparentid: and then the -Value of its child called idNote also that for all of these examples, it doesn't matter if it concern a hash table (dictionary) or a (PSCustom)Object or an array of any other list item.