How do I create an anonymous object in PowerShell? - powershell

I want to create an object of arbitrary values, sort of like how I can do this in C#
var anon = new { Name = "Ted", Age = 10 };

You can do any of the following, in order of easiest usage:
Use Vanilla Hashtable with PowerShell 5+
In PS5, a vanilla hash table will work for most use cases
$o = #{ Name = "Ted"; Age = 10 }
Convert Hashtable to PSCustomObject
If you don't have a strong preference, just use this where vanilla hash tables won't work:
$o = [pscustomobject]#{
Name = "Ted";
Age = 10
}
Using Select-Object cmdlet
$o = Select-Object #{n='Name';e={'Ted'}},
#{n='Age';e={10}} `
-InputObject ''
Using New-Object and Add-Member
$o = New-Object -TypeName psobject
$o | Add-Member -MemberType NoteProperty -Name Name -Value 'Ted'
$o | Add-Member -MemberType NoteProperty -Name Age -Value 10
Using New-Object and hashtables
$properties = #{
Name = "Ted";
Age = 10
}
$o = New-Object psobject -Property $properties;
Note: Objects vs. HashTables
Hashtables are just dictionaries containing keys and values, meaning you might not get the expected results from other PS functions that look for objects and properties:
$o = #{ Name="Ted"; Age= 10; }
$o | Select -Property *
Further Reading
4 Ways to Create PowerShell Objects
Everything you wanted to know about hashtables
Everything you wanted to know about PSCustomObject

Try this:
PS Z:\> $o = #{}
PS Z:\> $o.Name = "Ted"
PS Z:\> $o.Age = 10
Note: You can also include this object as the -Body of an Invoke-RestMethod and it'll serialize it with no extra work.
Update
Note the comments below. This creates a hashtable.

With PowerShell 5+
Just declare as:
$anon = #{ Name="Ted"; Age= 10; }

Related

How do I add a System.Collections.ArrayList to a PowerShell custom object?

My goal is to create a custom data object that has two discrete variables (fooName and fooUrl) and a list of fooChildren, each list item having two discrete variables variables childAge and childName.
Currently, I have this:
$fooCollection = [PSCustomObject] #{fooName=""; fooUrl=""; fooChildrenList=#()}
$fooCollection.fooName = "foo-a-rama"
$fooCollection.fooUrl = "https://1.2.3.4"
$fooChild = New-Object -TypeName PSobject
$fooChild | Add-Member -Name childAge -MemberType NoteProperty -Value 6
$fooChild | Add-Member -Name childName -MemberType NoteProperty -Value "Betsy"
$fooCollection.fooChildrenList += $fooChild
$fooChild = New-Object -TypeName PSobject
$fooChild | Add-Member -Name childAge -MemberType NoteProperty -Value 10
$fooChild | Add-Member -Name childName -MemberType NoteProperty -Value "Rolf"
$fooCollection.fooChildrenList += $fooChild
cls
$fooCollection.fooName
$fooCollection.fooUrl
foreach ($fooChild in $fooCollection.fooChildrenList)
{
(" " + $fooChild.childName + " " + $fooChild.childAge)
}
Which produces the following. So far so good
foo-a-rama
https://1.2.3.4
Betsy 6
Rolf 10
Problem: I don't like using += because as I understand it, using += results in a copy of $fooCollection.fooChildrenList being created (in whatever state it's in) each time += is executed.
So, instead of implementing fooChildrenList as #(), I want to implement fooChildrenList as New-Object System.Collections.ArrayList so I can add each row as needed. I've tried various ways of doing this in code but fooChildrenList winds up being unpopulated. For example:
$fooCollection = [PSCustomObject] #{fooName=""; fooUrl=""; fooChildrenList = New-Object System.Collections.ArrayList}
$fooCollection.fooName = "foo-a-rama"
$fooCollection.fooUrl = "https://1.2.3.4"
$fooChild.childName = "Betsy"
$fooChild.childAge = 6
$fooCollection.fooChildrenList.Add((New-Object PSObject -Property $fooChild))
$fooChild.childName = "Rolf"
$fooChild.childAge = 10
$fooCollection.fooChildrenList.Add((New-Object PSObject -Property $fooChild))
$fooCollection | get-member shows
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()
fooChildrenList NoteProperty System.Collections.ArrayList fooChildrenList=
fooName NoteProperty string fooName=foo-a-rama
fooUrl NoteProperty string fooUrl=https://1.2.3.4
$fooCollection shows
fooName : foo-a-rama
fooUrl : https://1.2.3.4
fooChildrenList : {}
How do I add a System.Collections.ArrayList to a PowerShell custom object?
Well im not sure what issue you are getting it works fine for me
function New-Child([string]$Name, [int]$Age){
$Child = New-Object -TypeName PSobject
$Child | Add-Member -Name childAge -MemberType NoteProperty -Value $age -PassThru |
Add-Member -Name childName -MemberType NoteProperty -Value $name
return $child
}
$fooCollection = [PSCustomObject] #{fooName=""; fooUrl=""; fooChildrenList = New-Object System.Collections.ArrayList}
$fooCollection.fooName = "foo-a-rama"
$fooCollection.fooUrl = "https://1.2.3.4"
$fooCollection.fooChildrenList.Add((New-Child -Name "Betty" -Age 9)) | Out-Null
$fooCollection.fooChildrenList.Add((New-Child -Name "Ralf" -Age 15)) | Out-Null
$fooCollection.fooName
$fooCollection.fooUrl
foreach ($fooChild in $fooCollection.fooChildrenList)
{
" " + $fooChild.childName + " " + $fooChild.childAge
}
output
foo-a-rama
https://1.2.3.4
Betty 9
Ralf 15
The challenge is to add a copy of the $fooChild [pscustomobject] instance you're re-using every time you add to the list with .Add() (if you don't use a copy, you'll end up with all list elements pointing to the same object).
However, you cannot clone an existing [pscustomobject] (a.k.a [psobject]) instance with New-Object PSObject -Property.
One option (PSv3+) is to define the reusable $fooChild as an ordered hashtable instead, and then use a [pscustomobject] cast, which implicitly creates a new object every time:
$fooCollection = [PSCustomObject] #{ fooChildrenList = New-Object Collections.ArrayList }
# Create the reusable $fooChild as an *ordered hashtable* (PSv3+)
$fooChild = [ordered] #{ childName = ''; childAge = -1 }
# Create 1st child and add to list with [pscustomobject] cast
$fooChild.childName = 'Betsy'; $fooChild.childAge = 6
$null = $fooCollection.fooChildrenList.Add([pscustomobject] $fooChild)
# Create and add another child.
$fooChild.childName = 'Rolf'; $fooChild.childAge = 10
$null = $fooCollection.fooChildrenList.Add([pscustomobject] $fooChild)
# Output the children
$fooCollection.fooChildrenList
Note the $null = ..., which suppresses the typically unwanted output from the .Add() method call.
The above yields:
childName childAge
--------- --------
Betsy 6
Rolf 10
A slightly more obscure alternative is to stick with $fooChild as a [pscustomobject] instance and call .psobject.Copy() on it to create a clone.
ArcSet's helpful answer provides a more modular solution that creates new custom-object instances on demand via a helper function.
Finally, in PSv5+ you could define a helper class:
$fooCollection = [PSCustomObject] #{ fooChildrenList = New-Object Collections.ArrayList }
# Define helper class
class FooChild {
[string] $childName
[int] $childAge
}
# Create 1st child and add to list with [pscustomobject] cast
$null = $fooCollection.fooChildrenList.Add([FooChild] #{ childName = 'Betsy'; childAge = 6 })
# Create and add another child.
$null = $fooCollection.fooChildrenList.Add([FooChild] #{ childName = 'Rolf'; childAge = 10 })
# Output the children
$fooCollection.fooChildrenList
Note how instances of [FooChild] can be created by simply casting a hashtable that has entries matching the class property names.
Quick copy paste from something I have that I use to make some of my Arrays. I have to create the custom objects and then add them to the Array. It will need modified for your scenario but I think it will get you what you need.
[System.Collections.ArrayList]$SQL_Query_Results = #()
ForEach ($SQL_Index in $SQL_Table) {
$SQL_Array_Object = [PSCustomObject]#{
'Computer_Name' = $SQL_Table[$SQL_Index_Counter].ComputerID -replace ",", ""
'Project' = $SQL_Table[$SQL_Index_Counter].Project
'Site' = $SQL_Table[$SQL_Index_Counter].Site
'Description' = $SQL_Table[$SQL_Index_Counter].Description -replace ",", ""
'Physical_Machine' = $SQL_Table[$SQL_Index_Counter].IsPhysicalMachine
'Active' = $SQL_Table[$SQL_Index_Counter].IsActive
}
$SQL_Query_Results.Add($SQL_Array_Object) | Out-Null
}
Edited to show how Array was initially created.

$this object reference for nested custom objects in Powershell

There's something that I can't quite seem to wrap my head around when trying to do object references in Powershell. Not sure if there's something that I am missing out on.
A sample code illustrating this problem is as follows:
function Create-Custom-Object {
$oResult = New-Object -TypeName PSObject -Property (#{
"Test" = $(Get-Date);
})
Add-Member -memberType ScriptMethod -InputObject $oResult -Name "GetTest" -Value {
return $this.Test;
}
return $oResult
}
function Create-Wrapper-Object {
$oObject = $(Create-Custom-Object)
$oResult = New-Object -TypeName PSObject -Property (#{
"Object" = $oObject;
"Test" = $(Get-Date);
})
Add-Member -MemberType ScriptMethod -InputObject $oResult -Name "WrapTest" -Value {
return $this.Object.GetTest()
}
return $oResult
}
$oCustom = Create-Custom-Object
sleep 5
$oWrapper = Create-Wrapper-Object
echo "Custom-Test: $($oCustom.Test)"
echo "Wrapper-Test: $($oWrapper.Test)"
echo "GetTest: $($oCustom.GetTest())"
echo "WrapTest: $($oWrapper.WrapTest())"
When run, the output is as per below:
>powershell -file test.ps1
Custom-Test: 11/20/2017 16:10:19
Wrapper-Test: 11/20/2017 16:10:24
GetTest: 11/20/2017 16:10:19
WrapTest: 11/20/2017 16:10:24
What puzzled me is that the call to WrapTest() on the wrapper object returns the "Test" attribute value from the wrapper object instead of the embedded custom object. Why is Powershell behaving like this?
I suspect that the problem here (based on the assumed intent of the sleep 5) is that $oCustom is assigned a Custom-Object and then 5 seconds later $oWrapper is assigned a Wrapper-Object which contains a new Custom-Object with essentially the same [DateTime] value (to the nearest second), not the (intended?) previously created $oCustom. WrapTest() is not returning the Test member of $oWrapper but the indistinguishable Test member of its own Custom-Object in $oWrapper.Object. In order to create a (generic) wrapper object, you need something to wrap, otherwise it's really just a (specific) nested object. Something like this:
function Create-Wrapper-Object {
param ($ObjectToWrap)
$oResult = New-Object -TypeName PSObject -Property (#{
"Object" = $ObjectToWrap; # presumably with a GetTest() method
"Test" = $(Get-Date); # remember the time of wrapping
})
Add-Member -MemberType ScriptMethod -InputObject $oResult -Name "WrapTest" -Value {
return $this.Object.GetTest()
}
return $oResult
}
With the (assumed to be) desired result:
$oCustom = Create-Custom-Object
sleep 5
$oWrapper = Create-Wrapper-Object $oCustom
echo "Custom-Test: $($oCustom.Test)"
Custom-Test: 05/31/2021 08:52:30
echo "Wrapper-Test: $($oWrapper.Test)"
Wrapper-Test: 05/31/2021 08:52:35
echo "GetTest: $($oCustom.GetTest())"
GetTest: 05/31/2021 08:52:30
echo "WrapTest: $($oWrapper.WrapTest())"
WrapTest: 05/31/2021 08:52:30

PNP powershell SharePoint Online set modern page bannerimageurl

My goal is to use PNP commandlets to set the SharePoint online modern bannerimageurl property for a page.
First I get a list of the pages and their current title and bannerimageurl values
# Get alist of all pages and their banner URLs
$items = Get-PnPListItem -List "SitePages" -Fields ID,Title,BannerImageUrl
$items | %{new-object PSObject -Property #{Id=$_["ID"];Title=$_["Title"];BannerImageUrl=$_["BannerImageUrl"].Url}} | select ID,Title,BannerImageUrl
But even if I then run the following code to set the BannerImage of one page (say ID2)
Set-PnPListItem -List "SitePages" -Id 2 -Values #{"BannerImageUrl" = " https://contoso.sharepoint.com/sites/mycomsite3/bannerimages/bread-braid-tedster-sml.jpg";}
When I run the following again the item 2 shows up as having a changed BannerImageUrl
$items = Get-PnPListItem -List "SitePages" -Fields ID,Title,BannerImageUrl
$items | %{new-object PSObject -Property #{Id=$_["ID"];Title=$_["Title"];BannerImageUrl=$_["BannerImageUrl"].Url}} | select ID,Title,BannerImageUrl
BUT when I actually view the page in the browser that is item 2 there has been no change to the banner image ??
Please tell me what I'm doing wrong when I set the BannerImageUrl.
Your experience and knowledge greatly accepted.
I've written a PS Function for that exact same problem based on the JS Solution here, in case you still need it:
function UpdateBannerImage {
param(
[string]$listName,
[int]$itemId,
[string]$newBannerUrl
)
#get list item
$item = Get-PnPListItem -List $listName -Id $itemId -Fields LayoutWebpartsContent, BannerImageUrl
if($item["LayoutWebpartsContent"] -match 'data-sp-controldata="([^"]+)"'){
# get substring w/ regex
$temp = $item["LayoutWebpartsContent"] | Select-String -Pattern 'data-sp-controldata="([^"]+)"'
$content = $temp.Matches.Groups[1].Value
# replace [] bc sometimes it throws later
$content = $content.replace("[","[").replace("]","]")
# decode
$dec = [System.Web.HttpUtility]::HtmlDecode($content)
# from JSON
$jnContent = ConvertFrom-Json $dec
#set values
if (!$jnContent.serverProcessedContent) {
$jnContent.serverProcessedContent = {};
}
if (!$jnContent.serverProcessedContent.imageSources) {
$jnContent.serverProcessedContent.imageSources = New-Object PSObject;
$jnContent.serverProcessedContent.imageSources | add-member Noteproperty imageSource $newBannerUrl
}
if(!$jnContent.serverProcessedContent.imageSources.imageSource){
$jnContent.serverProcessedContent.imageSources | add-member Noteproperty imageSource $newBannerUrl
}
$jnContent.serverProcessedContent.imageSources.imageSource = $newBannerUrl
# need to always create new properties, otherwise nothing changes
$curTitle = "";
if($jnContent.properties){
$curTitle = $jnContent.properties.title;
}
$jnContent.properties = New-Object PSObject;
$jnContent.properties | add-member Noteproperty title $curTitle
$jnContent.properties | add-member Noteproperty imageSourceType 2
# to JSON
$newContent = $jnContent | ConvertTo-Json -Compress
$enc = [System.Web.HttpUtility]::HtmlEncode($newContent)
$enc = $enc.replace("{","{").replace(":",":").replace("}","}").replace("[","[").replace("]","]")
# replace full item property
$fullContent = $item["LayoutWebpartsContent"].replace("[","[").replace("]","]");
$fullContent = $fullContent -replace $content, $enc
$fullContent.replace("[","[").replace("]","]")
# set & update
$item["LayoutWebpartsContent"] = $fullContent
$item.Update()
# not really sure if needed, but also update bannerURL
Set-PnPListItem -List $listName -Id $itemId -Values #{"BannerImageUrl" = $newBannerUrl; }
}
}
new here sorry if i mess up the format, also uploaded for safety here :P

Is it possible to create custom (structured) datatype in powershell?

Is it possible to have custom datatype in powershell?
I would like to have a (structured) variable like below. Following thing is not syntactically correct.I just want to club following three variables.
Variable Databasedetails {
string DatabaseName
string TableName
string[] columnNames
}
If Powershell has provision to do it then i can have single variable array which can help me to write queries easily.
$Databasedetails = { "DataBaseName=DB-someX","TableName=TableX"," (ColumnNames={"Col1","col2"}"
"DataBaseName=DB-someY","TableName=TableY"," (ColumnNames={"Col2","colN"}"
}
Foreach ($DBdetails in $DataBaseDetails)
{
$query= "SELECT [$DBdetails.$columnName] FROM [$DBdetails$DatabaseName].[dbo].[$DBdetails.$TableName]"
invoke-sqlcmd -query $query -ServerInstance $srvInstance"
}
Is there any way to create structured variable and i can create array of that and process?
The easiest is to use hashtables and convert them to custom objects:
$result = #()
for (... my loop ... ) {
... retrieve data ...
$props = #{ Databasename = $dbname; Tablename = $tname; Cols = $colarray }
$result += (new-object pscustomobject -prop $props)
}
You will want to look into either Add-Member or Add-Type, the later being only in Powershell 2.0.
$details = New-Object PsObject
| Add-Member NoteProperty DatabaseName '' -pass
| Add-Member NoteProperty TableName '' -pass
| Add-Member NoteProperty ColumnNames #() -pass
or with Add-Type
Add-Type 'public class DatabaseDetails { public string DatabaseName; public string TableName; public string[] ColumnNames; }' -Language CSharp
$details = New-Object DatabaseDetails

How to create new clone instance of PSObject object

I want to create new instance of my custom PSObject. I have a Button object created as PSObject and I want to create new object Button2 which has the same members as Button does, but I can't find a way how to clone the original object without making it referenced in original object (if I change a property in Button2 it changes in Button as well). Is there a way how to do it similarly as with hashtables and arrays via some Clone() method?
Easiest way is to use the Copy Method of a PsObject ==> $o2 = $o1.PsObject.Copy()
$o1 = New-Object -TypeName PsObject -Property #{
Fld1 = 'Fld1';
Fld2 = 'Fld2';
Fld3 = 'Fld3'}
$o2 = $o1.PsObject.Copy()
$o2 | Add-Member -MemberType NoteProperty -Name Fld4 -Value 'Fld4'
$o2.Fld1 = 'Changed_Fld'
$o1 | Format-List
$o2 | Format-List
Output:
Fld3 : Fld3
Fld2 : Fld2
Fld1 : Fld1
Fld3 : Fld3
Fld2 : Fld2
Fld1 : Changed_Fld
Fld4 : Fld4
For some reason PSObject.Copy() doesn't work for all object types. Another solution to create a copy of an object is to convert it to/from Json then save it in a new variable:
$CustomObject1 = [pscustomobject]#{a=1; b=2; c=3; d=4}
$CustomObject2 = $CustomObject1 | ConvertTo-Json -depth 100 | ConvertFrom-Json
$CustomObject2 | add-Member -Name "e" -Value "5" -MemberType noteproperty
$CustomObject1 | Format-List
$CustomObject2 | Format-List
Indeed there is no clone method! However where there is a will...
$o = New-Object PsObject -Property #{ prop1='a' ; prop2='b' }
$o2 = New-Object PsObject
$o.psobject.properties | % {
$o2 | Add-Member -MemberType $_.MemberType -Name $_.Name -Value $_.Value
}
$o.prop1 = 'newvalue'
$o
$o2
Output:
prop2 prop1
----- -----
b newvalue
b a
Another possibility:
$o1 = New-Object PsObject -Property #{ prop1='a' ; prop2='b' }
$o2 = $o1 | select *
$o2.prop1 = 'newvalue'
$o1.prop1
$o2.prop1
a
newvalue
Here's a [pscustomobject] example with the hidden .psobject.copy():
$a = [pscustomobject]#{message='hi'}
$a.message
hi
$b = $a.psobject.copy()
$b.message
hi
$a.message = 'there'
$a.message
there
$b.message
hi
The Better way i found out was to use ConvertTo-Json and ConvertFrom-Json.
Ee -
Suppose you want to clone a object $toBeClonedObject, just run below code to clone.
$clonedObject = $toBeClonedObject | ConvertTo-Json | ConvertFrom-Json
Starting from PowerShell v5, you can use Class.
The problem with psobject.Copy() is, if you update the cloned object, then your template object's referenced properties will be also updated.
example:
function testTemplates
{
$PSCustomObjectTemplate = New-Object PSCustomObject -Property #{
List1 = [System.Collections.Generic.List[string]]#() # will be updated in template
String1 = "value1" # will not be updated in template
Bool1 = $false # will not be updated in template
}
$objectFromPSTemplate1 = $PSCustomObjectTemplate.psobject.Copy()
$objectFromPSTemplate1.List1.Add("Value")
$objectFromPSTemplate1.String1 = "value2"
$objectFromPSTemplate.Bool1 = $true
# $PSCustomObjectTemplate IS updated, so CANNOT be used as a clean template!
$PSCustomObjectTemplate
Class ClassTemplate {
[System.Collections.Generic.List[string]]$List1 = #() # will not be updated in template
[string]$String1 = "value1" # will not be updated in template
[bool]$Bool1 = $false # will not be updated in template
}
$objectFromClassTemplate = [ClassTemplate]::new()
$objectFromClassTemplate.List1.Add("Value")
$objectFromClassTemplate.String1 = "value2"
$objectFromClassTemplate.Bool1 = $true
# $ClassTemplate IS NOT updated, so can be used as a clean template!
[ClassTemplate]::new()
}
testTemplates
PS C:\Windows\system32> testTemplates
List1 String1 Bool1
----- ------- -----
{Value} value1 False
-> Template from PSCustomObject is updated (referenced property -List1)
List1 String1 Bool1
----- ------- -----
{} value1 False
-> Template from Class is safe
This usually works for me:
$Source = [PSCustomObject]#{ Value = 'Test' };
$Copy = ($Source | ConvertTo-Json) | ConvertFrom-Json;
Put this in a Utility class or define it in your current section
function clone($obj)
{
$newobj = New-Object PsObject
$obj.psobject.Properties | % {Add-Member -MemberType NoteProperty -InputObject $newobj -Name $_.Name -Value $_.Value}
return $newobj
}
Usage:
$clonedobj = clone $obj
Based on the answer by #TeraFlux, here's a function that will do a deep copy on multiple objects and accepts pipeline input.
Note, it leverages json conversion with a default depth of 100, which lends it to a few weaknesses
It's going to be slow on deep or complex objects, or objects with expensive (slow) pseudoproperties (methods pretending to be properties that are calculated on the fly when asked for)
Though it should still be faster than the Add-Member approach because the heavy lifting is going through a compiled function
Anything that can't be stored in JSON may get corrupted or left behind (methods will be a prime candidate for this type of error)
Though any object that can safely go through this process should be savable, able to be safely stored (for recovery) or exported for transportation
I would be interested in any caveats or improvements to deal with these
function Clone-Object {
[CmdletBinding()]
Param (
[Parameter(ValueFromPipeline)] [object[]]$objects,
[Parameter()] [int] $depth = 100
)
$clones = foreach( $object in $objects ){
$object `
| ConvertTo-Json `
-Compress `
-depth $depth `
| ConvertFrom-Json
}
return $clones
}
Here are some very basic unit tests
$testClone = {
$test1 = $null
$test2 = $null
$test3 = $null
$Test1 = [psCustomObject]#{a=1; b=2; c=3; d=4}
$Test2 = $Test1 | ConvertTo-Json -depth 100 | ConvertFrom-Json
$Test2 | add-Member -Name "e" -Value "5" -MemberType noteproperty
$Test3 = $test2 | Clone-Object
$Test3 | add-Member -Name "f" -Value "6" -MemberType noteproperty
$Test1.a = 7
$Test2.a = 8
#$Expected0 = [psCustomObject]#{a=1; b=2; c=3; d=4}
$Expected1 = [pscustomobject]#{a=7; b=2; c=3; d=4}
$Expected2 = [pscustomobject]#{a=8; b=2; c=3; d=4; e=5}
$Expected3 = [pscustomobject]#{a=1; b=2; c=3; d=4; e=5; f=6}
$results1 = #(); $results1+=$test1; $results1+=$expected1
$results2 = #(); $results2+=$test2; $results2+=$expected2
$results3 = #(); $results3+=$test3; $results3+=$expected3
$results1 | Format-Table # if these don't match then its probably passing references (copy not clone)
$results2 | Format-Table # if these don't match the core approach is incorrect
$results3 | Format-Table # if these don't match the function didn't work
}
&$testClone
Another option:
function Copy-Object($Object) {
$copy = #()
$Object.ForEach({
$currentObject = $_
$currentObjectCopy = New-Object $currentObject.GetType().Name
$currentObjectCopy.psobject.Properties.ForEach({
$_.Value = $currentObject.psobject.Properties[($_.Name)].Value
})
$copy += $currentObjectCopy
})
return $copy
}
Test objects:
class TestObjectA {
[string]$g
[int[]]$h
[string]getJ(){
return 'j'
}
}
class TestObjectB {
[string]$a
[int]$b
[hashtable]$c
[TestObjectA[]]$d
[string]getI(){
return 'i'
}
}
Tests:
$b = New-Object -TypeName TestObjectB -Property #{
a = 'value a'
b = 2
c = #{ e = 'value e'; f = 3 }
d = New-Object -TypeName TestObjectA -Property #{
g = 'value g'
h = #(4,5,6)
}
}
$bCopy = Copy-Object $b
# test with simple comparison
-not $(Compare-Object $b $bCopy)
True
# test json deep conversion output
$bJson = $b | ConvertTo-Json -Depth 10
$bCopyJson = $bCopy | ConvertTo-Json -Depth 10
-not $(Compare-Object $bJson $bCopyJson)
True
# test methods are intact
$bCopy.getI()
i
$bCopy.d.GetJ()
j
# test objects are seperate instances
$bCopy.b = 3
$b.b
2
$bCopy.b
3
Here's my version using Clixml
function Get-PSObjectClone {
param ( [psobject] $InputObject )
$_temp = New-TemporaryFile
$InputObject | Export-Clixml -Path $_temp -Depth 100
$_object = Import-Clixml -Path $_temp
Remove-Item $_temp -Force
Write-Output $_object
}
Works with everything I've thrown at it
Since Select-Object -Property expands wildcards in property names, a simple way to shallow-clone is this:
# Set up object
$o1 = [PSCustomObject]#{
Fld1 = 'Fld1';
Fld2 = 'Fld2';
Fld3 = 'Fld3'}
# Clone
$o2 = $o1 | Select-Object -Property *;
# Tests
$o1 -eq $o2;
$o1 | Format-List;
$o2 | Format-List;