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;
Related
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.
Let's say you have two objects that are identical (meaning they have the same properties and the same values respectively).
How do you test for equality?
Example
$obj1 & $obj2 are identical
Here's what I've tried:
if($obj1 -eq $obj2)
{
echo 'true'
} else {
echo 'false'
}
# RETURNS "false"
if(Compare-Object -ReferenceObject $obj1 -DifferenceObject $obj2)
{
echo 'true'
} else {
echo 'false'
}
# RETURNS "false"
Edit
This is not identical
You can compare two PSObject objects for equality of properties and values by using Compare-Object to compare the Properties properties of both PSObjectobjects. Example:
if ( -not (Compare-Object $obj1.PSObject.Properties $obj2.PSObject.Properties) ) {
"object properties and values match"
}
else {
"object properties and values do not match"
}
If you want it in a function:
function Test-PSCustomObjectEquality {
param(
[Parameter(Mandatory = $true)]
[PSCustomObject] $firstObject,
[Parameter(Mandatory = $true)]
[PSCustomObject] $secondObject
)
-not (Compare-Object $firstObject.PSObject.Properties $secondObject.PSObject.Properties)
}
I'd suggest using Compare-Object for this task:
Function Test-Objects
{
Param(
[Parameter(Mandatory,Position=0)]
[PSCustomObject]$Obj1,
[Parameter(Mandatory,Position=1)]
[PSCustomObject]$Obj2
)
[Void](Compare-Object -ReferenceObject $Obj1.PSObject.Properties -DifferenceObject.PSObject.Properties $Obj2 -OutVariable 'Test')
## Tests whether they are equal, no return = success
If (-not $Test)
{
$True
}
Else
{
$False
}
}
PS C:\> $Obj1 = [PSCustomObject]#{
Property1 = 'Value1'
Property2 = 'Value2'
Property3 = 'Value3'
Property4 = 'Value4'
Property5 = 'Value5'
}
PS C:\> $Obj2 = [PSCustomObject]#{
Property1 = 'Value1'
Property2 = 'Value2'
Property3 = 'Value3'
Property4 = 'Value4'
Property5 = 'Value5'
}
PS C:\> Test-Objects $Obj1 $Obj2
True
PS C:\> $Obj2 | Add-Member -MemberType 'NoteProperty' -Name 'Prop6' -Value 'Value6'
PS C:\> Test-Objects $Obj1 $Obj2
False
If you'd like to test for equality for every object property, one at a time, in order to compare and contrast two objects and see which individual pieces are different, you can use the following function, adapted from this article on how to compare all properties of two objects in Windows PowerShell
Function Compare-ObjectProperties {
Param(
[PSObject]$leftObj,
[PSObject]$rightObj
)
$leftProps = $leftObj.PSObject.Properties.Name
$rightProps = $rightObj.PSObject.Properties.Name
$allProps = $leftProps + $rightProps | Sort | Select -Unique
$props = #()
foreach ($propName in $allProps) {
# test if has prop
$leftHasProp = $propName -in $leftProps
$rightHasProp = $propName -in $rightProps
# get value from object
$leftVal = $leftObj.$propName
$rightVal = $rightObj.$propName
# create custom output -
$prop = [pscustomobject] #{
Match = $(If ($propName -eq "SamAccountName" ) {"1st"} Else {
$(If ($leftHasProp -and !$rightHasProp ) {"Left"} Else {
$(If ($rightHasProp -and !$leftHasProp ) {"Right"} Else {
$(If ($leftVal -eq $rightVal ) {"Same"} Else {"Diff"})
})
})
})
PropName = $propName
LeftVal = $leftVal
RightVal = $rightVal
}
$props += $prop
}
# sort & format table widths
$props | Sort-Object Match, PropName | Format-Table -Property `
#{ Expression={$_.Match}; Label="Match"; Width=6},
#{ Expression={$_.PropName}; Label="Property Name"; Width=25},
#{ Expression={$_.LeftVal }; Label="Left Value"; Width=40},
#{ Expression={$_.RightVal}; Label="Right Value"; Width=40}
}
And then use like this:
$adUser1 = Get-ADUser 'Grace.Hopper' -Properties *
$adUser2 = Get-ADUser 'Katherine.Johnson' -Properties *
Compare-ObjectProperties $adUser1 $adUser2
Couple Interesting Notes:
How to Test if Element Has Property
How to Get Property Value by Name
How to Create a Custom PS Object
How to Create a Nested Conditional / Ternary Operator
How to Format Table with fixed Widths
Attempted to Colorize output with VT Escape Sequences or Write-PSObject, but couldn't get it to work with fixed column widths which took priority
Here's the function I used:
function Test-ObjectEquality {
param(
[Parameter(Mandatory = $true)]
$Object1,
[Parameter(Mandatory = $true)]
$Object2
)
return !(Compare-Object $Object1.PSObject.Properties $Object2.PSObject.Properties)
}
Examples:
PS C:\> $obj1 = [pscustomobject] #{ 'a' = '5'; 'b' = 7; };
PS C:\> $obj2 = [pscustomobject] #{ 'a' = '5'; 'b' = 7; };
PS C:\> Test-ObjectEquality $obj1 $obj2
True
PS C:\> $obj2 = [psobject] #{ 'a' = '5'; 'b' = 7; };
PS C:\> Test-ObjectEquality $obj1 $obj2
False
PS C:\> $obj2 = New-Object -TypeName PSObject -Property #{ 'a' = '5'; 'b' = 7; };
PS C:\> Test-ObjectEquality $obj1 $obj2
True
PS C:\> $obj2 = [pscustomobject] #{ 'c' = '6'; 'b' = 7; };
PS C:\> Test-ObjectEquality $obj1 $obj2
False
PS C:\> $obj2 = [pscustomobject] #{ 'a' = '5'; 'b' = 8; };
PS C:\> Test-ObjectEquality $obj1 $obj2
False
PS C:\> $obj2 = [pscustomobject] #{ 'a' = '5'; 'b' = 7; c = 8 };
PS C:\> Test-ObjectEquality $obj1 $obj2
False
PS C:\> $obj2 = [pscustomobject] #{ 'a' = '5'; 'b' = '7'; };
PS C:\> Test-ObjectEquality $obj1 $obj2
False
I certainly believe it's possible for this to miss things; however, if you look at what's in Properties you can see what's being compared for every property on an object:
PS C:\> $obj1.PSObject.Properties | Select-Object -First 1
MemberType : NoteProperty
IsSettable : True
IsGettable : True
Value : 5
TypeNameOfValue : System.String
Name : a
IsInstance : True
It's not often that I've cared about more than the MemberType, Name, TypeNameOfValue, or Value of an object's properties.
Also, note that if you really need to, you can compare .PSObject.Members instead of .PSObject.Properties. That will compare properties and methods, although you're only comparing the method calls and not the method definitions.
I wrote a function that checks for exact equality:
function Global:Test-IdenticalObjects
{
param(
[Parameter(Mandatory=$true)]$Object1,
[Parameter(Mandatory=$true)]$Object2,
$SecondRun=$false
)
if(-not ($Object1 -is [PsCustomObject] -and $Object2 -is [PsCustomObject))
{
Write-Error "Objects must be PsCustomObjects"
return
}
foreach($property1 in $Object1.PsObject.Properties)
{
$prop1_name = $property1.Name
$prop1_value = $Object1.$prop1_name
$found_property = $false
foreach($property2 in $Object2.PsObject.Properties)
{
$prop2_name = $property2.Name
$prop2_value = $Object2.$prop2_name
if($prop1_name -eq $prop2_name)
{
$found_property = $true
if($prop1_value -ne $prop2_value)
{
return $false
}
}
} # j loop
if(-not $found_property) { return $false }
} # i loop
if($SecondRun)
{
return $true
} else {
Test-IdenticalObjects -Object1 $Object2 -Object2 $Object1 -SecondRun $true
}
} # function
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
I am building a multi-step local search engine with powershell that also allows you to email selected pieces of information.
I have got the search engine part down and the email part down, I just need to get the select part down.
So right now, you open the program and it prompts you to search for what you want. If I put in the query when, this is what is returned:
Mode LastWriteTime Length Name
---- ------------- ------ ----
-a--- 1/25/2017 3:29 PM 8357890 01 - Kiss Me When I'm Down.mp3
-a--- 1/24/2017 2:15 PM 7189290 09 - When You Love Someone.mp3
Now the goal is to select let's say 01 - Kiss Me When I'm Down.mp3, because I'm then going to put that into an $attachment variable, which will then send the song as an attachment. Is this achievable?
EDIT for clarity:
I've tried working with Select-Object to do this, but I can't get it to allow the user to select the song that they want. That is the goal of this, to allow the user to select the input they want.
This is an inelegant solution that adds an Index NoteProperty using the Add-Member cmdlet. As an example I used Get-ChildItem results:
$Items = Get-ChildItem C:\
$Index = 1
$Count = $Items.Count
foreach ($Item in $Items) {
$Item | Add-Member -MemberType NoteProperty -Name "Index" -Value $Index
$Index++
}
$Items | Select-Object Index, Attributes, LastWriteTime, Name | Out-Host
$Input = Read-Host "Select an item by index (1 to $Count)"
$Selected = $Items[$Input - 1]
Write-Host "You have selected $Selected"
I realise some good answers have been given, but the OP's post got me thinking about extracting the meta data for the MP3s...:
function getMP3Details() {
param ( [System.IO.FileInfo] $mp3file = $null )
[System.__ComObject] $Local:objShell = $null;
[System.__ComObject] $Local:objFolder = $null;
[System.__ComObject] $Local:objFile = $null;
[HashTable] $Local:objTags = #{ 0 = 'Name'; 1 = 'Size'; 13 = 'Artists'; 14 = 'Album'; 15 = 'Year'; 16 = 'Genre'; 20 = 'Authors'; 21 = 'Title'; 28 = 'Bit Rate'; }
[Int32] $Local:intTagIndex = 0;
[String] $Local:strTagName = '';
[String] $Local:strTagValue = '';
[PSCustomObject] $Local:objOutput = $null;
try {
if ( $mp3file -ne $null ) {
$objShell = New-Object -ComObject Shell.Application;
$objFolder = $objShell.NameSpace( $mp3file.DirectoryName );
$objFile = $objFolder.ParseName( $mp3file.Name );
$objOutput = New-Object -TypeName PSCustomObject;
foreach ( $intTagIndex in ($objTags.Keys | Sort-Object) ) {
$strTagName = $objTags[$intTagIndex];
$strTagValue = $objFolder.GetDetailsOf( $objFile, $intTagIndex );
Add-Member -InputObject $objOutput -MemberType NoteProperty -Name $strTagName -Value ( [String] ($strTagValue -replace '[^ -~]', '') );
} #foreach
Write-Output -InputObject $objOutput;
} #if
} #try
catch [System.Exception] {
# Do something.
'Error';
} #catch
return;
}
[String] $Local:strFolder = '<PATH TO ALBUM>';
[PSCustomObject[]] $Local:arrMP3Tracks = #();
[PSCustomObject] $Local:objSelectedTrack = $null;
try {
Get-ChildItem -LiteralPath $strFolder -File -Filter *.mp3 | Foreach-Object {
$arrMP3Tracks += getMP3Details -mp3file $_;
} #Foreach-Object
$objSelectedTrack = $arrMP3Tracks | Out-GridView -PassThru;
} #try
catch [System.Exception] {
# Do something.
'Error';
} #catch
exit 0;
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; }