Powershell, losing custom object attributes in hash table - powershell

I'm trying to make a collection of custom objects in powershell and store them in a hashtable. The problem is that the custom attributes disapear when I put the object into a hashtable.
$customObject = New-Object object
$customObject | Add-member -membertype noteproperty -name customValue -value "test"
Write-Host $customObject.customValue
$hashTable = #{}
$hashTable.add("custom", $customObject)
$object = $hashTable["custom"]
$object.customValue = 7
When I execute this code I get the following output.
test
Property 'customValue' cannot be found on this object; make sure it exists and is settable.
At C:\temp\test2.ps1:15 char:9
+ $object. <<<< customValue = 7
+ CategoryInfo : InvalidOperation: (:) [], RuntimeException
+ FullyQualifiedErrorId : PropertyAssignmentException
Is there some way I can use the custom attribute after I've placed it into a collection?

I am using PowerShell 3.0 on 64-bit Windows 7. In 3.0 your code runs as expected, but in 2.0 (powershell.exe -Version 2.0) I get the same error as you. What's really strange is this output under 2.0:
PS> [Object]::ReferenceEquals($customObject, $object)
True
PS> $customObject | Get-Member
TypeName: System.Object
Name MemberType Definition
---- ---------- ----------
Equals Method bool Equals(System.Object obj)
GetHashCode Method int GetHashCode()
GetType Method type GetType()
ToString Method string ToString()
customValue NoteProperty System.String customValue=test
PS> $object | Get-Member
TypeName: System.Object
Name MemberType Definition
---- ---------- ----------
Equals Method bool Equals(System.Object obj)
GetHashCode Method int GetHashCode()
GetType Method type GetType()
ToString Method string ToString()
So, PowerShell agrees that they're the same object, yet only one has a customValue property. I also notice that if I change the way you are adding $customObject to $hashTable from this...
$hashTable.add("custom", $customObject)
...to this...
$hashTable["custom"] = $customObject
...then your code works as expected under PowerShell 2.0. So, it seems like something is going wrong in the call to Add(), and that behavior must have been fixed in 3.0.
Another workaround is to change the first line from this...
$customObject = New-Object object
...to this...
$customObject = New-Object PSObject
...and your code runs without error in both versions of PowerShell. You can then shorten the first two lines to this...
$customObject = New-Object PSObject -Property #{ customValue = "test" }

Related

Parsing strings before character in Powershell

I want to obtain the environment, project name and location from a string and store it in a variable in Powershell.
The string is in the format of env-project-location. Exampleuat-hrapp-westeurope
How do I filter the string and store the outputs in a variable?
$environment = "uat"
$project = "hrapp"
$location = "westeurope"
You don't need regex for this, assuming the string will always have the same naming convention a simple split would do it:
$environment, $project, $location = 'uat-hrapp-westeurope'.Split('-')
Santiago Squarzon's post got me thinking so I did a little googling and found that you can also use this method if you want a PSCustomObject vs independent variables.
Clear-Host
$Base = "uat-hrapp-westeurope"
$CFSArgs = #{PropertyNames = "Environment", "Project", "Location"
Delimiter = '-'}
$obj = $Base | ConvertFrom-String #CFSArgs
$Obj
Output:
Environment Project Location
----------- ------- --------
uat hrapp westeurope
PS> $obj | gm
TypeName: System.Management.Automation.PSCustomObject
Name MemberType Definition
---- ---------- ----------
Equals Method bool Equals(System.Object obj)
GetHashCode Method int GetHashCode()
GetType Method type GetType()
ToString Method string ToString()
Environment NoteProperty string Environment=uat
Location NoteProperty string Location=westeurope
Project NoteProperty string Project=hrapp

Append parameters to an existing PSCustomObject 'op_Addition' error in powershell

Trying to parse through an Azure parameters template from gihub and update some parameters to the file in Powershell then commit it back to github for automation purposes. I seem to be erroring out on adding the new parameters back in to the file pulled from github. I've checked and made sure the objects are both the same.
I pull the JSON file down from github fine, convert it from JSON, compile my new parameter object and when i go to add the new parameters back into the original i receive the below error:
Method invocation failed because [System.Management.Automation.PSObject] does not contain a method named 'op_Addition'.
At line:1 char:1
+ $paramTemplate.parameters += $newparam
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidOperation: (op_Addition:String) [], RuntimeException
+ FullyQualifiedErrorId : MethodNotFound
Powerhell code:
#Get Content
$paramContent = (Invoke-WebRequest -Uri $parameterUri -Headers $headers -UseBasicParsing).content
$paramTemplate = $paramContent | ConvertFrom-Json
#define parameters in JSON Format
$addnewparam = #"
{
"parameters": {
"virtualMachineRG": {
"value": "$virtualMachineRG"
},
"virtualMachineName": {
"value": "$virtualMachineName"
},
"virtualMachineSize" : {
"value": "$virtualMachineSize"
},
"diagnosticsStorageAccountName": {
"value": "$diagnosticsStorageAccountName"
}
}
}
"#
$newparam = $addnewparam | ConvertFrom-JSON
$paramTemplate.parameters += $newparam
Any insight is greatly appreciated!
PS C:\GitHub\Azure> $paramtemplate.GetType()
>>
IsPublic IsSerial Name BaseType
-------- -------- ---- --------
True False PSCustomObject System.Object
PS C:\GitHub\Azure> $newparam.GetType()
IsPublic IsSerial Name BaseType
-------- -------- ---- --------
True False PSCustomObject System.Object
I've also tried matching the NoteProperty the same for both to no avail. I'm on PSVersion 5.1.17763.771
PS C:\GitHub\Azure> $paramtemplate | get-member
TypeName: System.Management.Automation.PSCustomObject
Name MemberType Definition
---- ---------- ----------
Equals Method bool Equals(System.Object obj)
GetHashCode Method int GetHashCode()
GetType Method type GetType()
ToString Method string ToString()
$schema NoteProperty string $schema=https://schema.management.azure.com/schemas/2015-01-01/deploymentParameters.json#
contentVersion NoteProperty string contentVersion=1.0.0.0
parameters NoteProperty System.Management.Automation.PSCustomObject parameters=#{subnetName=; virtualNetworkId=; virtualMachineName=; virtualMachineRG=; osDiskType=; virtualMachineSize=; admi...
PS C:\GitHub\Azure> $newparam | Get-Member
TypeName: System.Management.Automation.PSCustomObject
Name MemberType Definition
---- ---------- ----------
Equals Method bool Equals(System.Object obj)
GetHashCode Method int GetHashCode()
GetType Method type GetType()
ToString Method string ToString()
parameters NoteProperty System.Management.Automation.PSCustomObject parameters=#{virtualMachineRG=; virtualMachineName=; virtualMachineSize=; diagnosticsStorageAccountName=}
You can do the following if you want to update $paramtemplate.parameters property values with values from $newparam.parameters properties.
$newparam.parameters.PSObject.Properties.Name | Foreach-Object {
$paramtemplate.parameters.$_ = $newparam.parameters.$_
}
Since you are dealing with a PSCustomObject type in .parameters, .PSObject.Properties will return all of its properties. The .Name property returns the string name of the properties. $_ is the current object being processed within Foreach-Object { } script block.
You could do this slightly differently, which is uglier (IMO) but could be negligibly more performant.
$newparam.parameters.PSObject.Properties | Foreach-Object {
$paramtemplate.parameters.$($_.Name) = $_.Value
}
Note: If $newparam contains properties that $paramtemplate does not have, then we will have to add more logic to handle that case.
$newparam.parameters.PSObject.Properties | Foreach-Object {
if ($paramtemplate.parameters.PSObject.Properties.Name -contains $_.Name) {
$paramtemplate.parameters.$($_.Name) = $_.Value
}
else {
$paramtemplate.parameters | Add-Member -Type NoteProperty -Name $_.Name -Value $_.Value
}
}
Regarding the error message you received, this happened because you were attempting to add a PSCustomObject to another PSCustomObject. That type of operation requires your first object to be a collection. You can convert an object to a collection using the unary operator , just before you add the next object. The syntax is as follows:
$object1 = ,$object1 + $object2
EDIT
Looking more closely at these objects, it appears that all of the properties contain another PSCustomObject with a property called Value with some value that you will have supplied. In that case, the following will work for updating the template.
$newparam.parameters.PSObject.Properties | Foreach-Object {
if ($paramtemplate.parameters.PSObject.Properties.Name -contains $_.Name) {
$paramtemplate.parameters.$($_.Name).Value = $_.Value.Value
}
else {
$paramtemplate.parameters | Add-Member -Type NoteProperty -Name $_.Name -Value ([pscustomobject]#{Value = $_.Value.Value})
}
}
Try this:
$newparam | ForEach {
$paramtemplate.parameters | Add-Member -MemberType NoteProperty -Name $_.Name -Value $_.Value
}
This iterates over the psobject to add and adds properties one by one.

Just get the type of Powershell Object [collection?] (not the methods etc) (also: what's going on here?)

I am setting the variable 'a' like this:
$a=dir -recurse c:\temp
If I now examine this object with 'get-member' like this:
$a|get-member
I get back the type, but also all the methods and other properties like this:
TypeName: System.IO.FileInfo
Name MemberType Definition
---- ---------- ----------
Mode CodeProperty System.String Mode{get=Mode;}
AppendText Method System.IO.StreamWriter AppendText()
CopyTo Method System.IO.FileInfo CopyTo(string destFileName), System.IO.FileInfo CopyTo(string destFileName, bool...
[...]
Which is nice; but sometimes I just want to get hold of the type (I'll look up whatever it does afterwards).
So I tried this:
$a|get-member|select-object -Property typename
The output suprised me initially: because what you get back is the typename for each individual item in the collection- and the types (although clearly related) are not identical:
TypeName
--------
System.IO.DirectoryInfo
System.IO.DirectoryInfo
System.IO.DirectoryInfo
[...]
System.IO.FileInfo
[...]
Then I thought about this; and it sort of made sense - this is a collection of Objects that I'm piping through the Object-Pipeline; but then it made me think:
What was 'Get-Member' actually telling me previously ? When it said the type was 'System.IO.FileInfo' - but actually the collection contains a mixture of object types ?
Whatever it is 'Get-Member' is displaying - how do I get at that exact thing ?
I can almost (sort-of, but actually its just wrong) get what I thought I was initially after with this:
$a|get-member|select-object -Property typename -first 1
But this just peeks at the 'first' object; and in fact gives me a different answer to what 'Get-Member' output for me.
So what is the 'TypeName' that 'Get-Member' is showing- and where is that stored ?
Is the value of 'dir' (Get-ChildItem against a filepath) simply a collection of objects, or is it a parent object (with its own set of 'scalar' properties) and a single property referencing a collection of associated objects ?
That's a lot of questions in one, let's see if we can make some sense of this.
The TypeName that Get-Member displays for each distinct type of object (we'll get back to that), comes from a hidden property that all objects in PowerShell carry, called pstypenames:
PS C:\> $something = 1
PS C:\> $something.pstypenames
System.Int32
System.ValueType
System.Object
PS C:\>
So, pstypenames is an ordered list of all the types in that objects type hierarchy. If we change the value of pstypenames, you'll see that reflected in the output from Get-Member:
PS C:\> $something.pstypenames.Insert(0,"MonoJohnny.CustomTypeName")
PS C:\> Get-Member -InputObject $something
TypeName: MonoJohnny.CustomTypeName
Name MemberType Definition
---- ---------- ----------
...
So, if you want the TypeName for an object, as displayed by Get-Member, you can always do:
$something.pstypenames[0]
As shown above, this value can be manipulated, so if you want the true type of an object at runtime, use the GetType() method:
$something.GetType().FullName
The reason that Get-Member only shows you the entire list of properties for a System.IO.FileInfo object once is that it (rightly so) assumes that all other objects of type System.IO.FileInfo will have the exact same members - no need to duplicate that output.
I you have multiple distinct types in a collection and pipe those to Get-Member, it'll only show you the members for the first object it encounters with a unique type name (remember, the value of pstypenames[0]). This is the case when you pipe Get-ChildItem to Get-Member, since Get-ChildItem on the filesystem provider only returns two different types of objects - FileInfo objects and DirectoryInfo objects.
For builtin types this is totally fine, but with custom objects that you create in PowerShell, this can be quite annoying.
Consider the following example:
PS C:\> $Object1 = New-Object psobject -Property #{ Prop1 = 1 }
PS C:\> $Object2 = New-Object psobject -Property #{ Prop2 = 2 }
Now, $Object1 and $Object2 are clearly 2 different kinds of objects - they have different property names. But what happens when we pipe them to Get-Member in the same pipeline:
PS C:\> $Object1,$Object2 |Get-Member
TypeName: System.Management.Automation.PSCustomObject
Name MemberType Definition
---- ---------- ----------
Equals Method bool Equals(System.Object obj)
GetHashCode Method int GetHashCode()
GetType Method type GetType()
ToString Method string ToString()
Prop1 NoteProperty System.Int32 Prop1=1
Since the underlying type of both objects are System.Management.Automation.PSObject, the value of pstypenames is also the same for both, and Get-Member can't distinguish between the two.
Now, all of a sudden, the ability to manipulate pstypenames without actually fiddling with the type system comes in handy:
PS C:\> $Object1.pstypenames.Insert(0,"ObjectType1")
PS C:\> $Object2.pstypenames.Insert(0,"ObjectType2")
PS C:\> $Object1,$Object2 |Get-Member
TypeName: ObjectType1
Name MemberType Definition
---- ---------- ----------
Equals Method bool Equals(System.Object obj)
GetHashCode Method int GetHashCode()
GetType Method type GetType()
ToString Method string ToString()
Prop1 NoteProperty System.Int32 Prop1=1
TypeName: ObjectType2
Name MemberType Definition
---- ---------- ----------
Equals Method bool Equals(System.Object obj)
GetHashCode Method int GetHashCode()
GetType Method type GetType()
ToString Method string ToString()
Prop2 NoteProperty System.Int32 Prop2=2
Sweet!
You can also inject a custom type name during object creation by specifying PSTypeName as a string property:
PS C:\> $Object1 = New-Object psobject -Property #{ Prop1 = 1; PSTypeName = 'ObjectType1' }
PS C:\> $Object2 = New-Object psobject -Property #{ Prop2 = 2; PSTypeName = 'ObjectType2' }

powershell outputs argument with (#{Name=name}:String)

I'm trying to run the command Get-VMNetworkAdapter on a list of VMs
I'm getting the list with the command:
Get-VM –ComputerName (Get-ClusterNode –Cluster clustername)|select name
and it looks fine, when I'm using
$vmm=Get-VM –ComputerName (Get-ClusterNode –Cluster clustername)|select name
foreach ($item in $vmm)
{Get-VMNetworkAdapter -VMName $item}
it gives me the exception
nvalidArgument: (#{Name=vmname}:String)
like it adds all those symbols..
What is the proper way to lose them?
You need to expand the property. Select doesn't remove the object otherwise:
$vmm = Get-VM –ComputerName (Get-ClusterNode –Cluster clustername) `
| Select-Object -ExpandProperty name
To explain what -ExpandProperty does:
First of all, the drawback of -ExpandProperty is that you can only do it to one property at a time.
Select-Object normally wraps the results in another object so that properties remain properties. If you say $x = Get-ChildItem C:\Windows | Select-Object Name, then you get an object array with one property: Name.
PS C:\> $x = Get-ChildItem C:\Windows | Select-Object Name
PS C:\> $x
Name
----
45235788142C44BE8A4DDDE9A84492E5.TMP
8A809006C25A4A3A9DAB94659BCDB107.TMP
.
.
.
PS C:\> $x[0].Name
45235788142C44BE8A4DDDE9A84492E5.TMP
PS C:\> $x[0].GetType().FullName
System.Management.Automation.PSCustomObject
Notice the header? Name is a property of the object.
Also, the base object with it's type is still kind of there:
PS C:\> $x | Get-Member
TypeName: Selected.System.IO.DirectoryInfo
Name MemberType Definition
---- ---------- ----------
Equals Method bool Equals(System.Object obj)
GetHashCode Method int GetHashCode()
GetType Method type GetType()
ToString Method string ToString()
Name NoteProperty string Name=45235788142C44BE8A4DDDE9A84492E5.TMP
TypeName: Selected.System.IO.FileInfo
Name MemberType Definition
---- ---------- ----------
Equals Method bool Equals(System.Object obj)
GetHashCode Method int GetHashCode()
GetType Method type GetType()
ToString Method string ToString()
Name NoteProperty string Name=bfsvc.exe
Normally, that's all great. Especially because we normally want multiple properties of the object.
Sometimes, however, not what we want. Sometimes, we want an array that's the same type as the property we selected. When we use it later we want just that property and nothing else and we want it to be the exact same type as the property and nothing else.
PS C:\> $y = Get-ChildItem C:\Windows | Select-Object -ExpandProperty Name
PS C:\> $y
45235788142C44BE8A4DDDE9A84492E5.TMP
8A809006C25A4A3A9DAB94659BCDB107.TMP
.
.
.
PS C:\> $y[0].Name
PS C:\> $y[0]
45235788142C44BE8A4DDDE9A84492E5.TMP
PS C:\> $y.GetType().FullName
System.Object[]
PS C:\> $y[0].GetType().FullName
System.String
Notice there's no header, and any calls to a Name property fail; there is no Name property anymore.
And, there's nothing left over from the original object:
PS C:\> $y | Get-Member
TypeName: System.String
Name MemberType Definition
---- ---------- ----------
Clone Method System.Object Clone(), System.Object ICloneable.Clone()
.
.
.
.
Basically, here it's the equivalent of doing this:
$z = Get-ChildItem C:\Windows | ForEach-Object { $_.Name }
Which I think is how you had to do it in PowerShell v1.0 or v2.0... it's been too many years since I've used that to remember right.

Using a Powershell noteproperty as a text string in a variable

I've used Invoke-Restmethod to download some data, which Powershell stores in a PSCustomObject, in a property called data.
I need to use the value of one of the items in the returned data as a variable for another command. I have managed to select-object -expand my way down to the following output from Get-Member:
Name MemberType Definition
---- ---------- ----------
Equals Method bool Equals(System.Object obj)
GetHashCode Method int GetHashCode()
GetType Method type GetType()
ToString Method string ToString()
id NoteProperty System.Int32 id=999
What I need to do is grab the value of the ID noteproperty - 999 - and pass that as part of a string to a new variable, eg:
$newVar = "sometext" + 999 + "moretext"
No amount of select-string or out-string etc is helping. Scripting is not exactly my strong point so I'm not sure I'm even articulating what I want properly - apologies if this is the case!
Any assistance much appreciated
I'm not sure exactly what your code and looks like, so I created the following static approximation from the description:
$data = New-Object PSCustomObject
$data | Add-Member -Type NoteProperty -Name Id -Value 999
$restResponse = New-Object PSCustomObject
$restResponse | Add-Member -Type NoteProperty -Name data -Value $data
Please clarify if this is not a match. You can get the Id value as follows
$restResponse.data.Id
Assign it to another variable
$newVar = "sometext" + $restResponse.data.Id + "moretext"
$newVar
And if your REST response is a collection of data objects, iterate through them
$restResponse.data | Foreach-Object { "sometext" + $_.Id + "moretext" }
I would go for for using $output | select *,#{n='test';e={[string]$_.test}} -exclude properties test
if the exclude is not active it will complain about it already exists. Mostly I use the select expression to manipulate data realtime instead of psCustomObject for such simple task