How do you use the default values on a PSCustomObject's ScriptMethod - powershell

I am trying to specify the value of the third parameter of the method, while still letting the second parameter in the method default.
I was able to piece this together to get it working, but I was hoping someone else had a better solution
$o=[PSCustomObject]#{};
Add-Member -MemberType ScriptMethod -InputObject $o -Name 'WrapText' -Value {
param($S,$Open='"',$Close)
if($Close){
"$Open$S$Close"
}else{
"$Open$S$Open"
}
}
$DefaultValues = #{};
$o.WrapText.Script.Ast.ParamBlock.Parameters | %{
$DefaultValues.($_.Name.ToString()) = $_.DefaultValue.Value
}
$o.WrapText('Some Text',$DefaultValues.'$Open','|')

In order to check whether an argument was bound to a parameter, you'll want to use $PSBoundParameters:
Add-Member -MemberType ScriptMethod -InputObject $o -Name 'WrapText' -Value {
param($S,$Open='"',$Close='"')
if($PSBoundParameters.ContainsKey('Close')){
"$Open$S$Close"
}else{
"$Open$S$Open"
}
}
Now the if condition is only $true if a third argument is supplied:
PS ~> $o.WrapText('abc')
"abc"
PS ~> $o.WrapText('abc',"'")
'abc'
PS ~> $o.WrapText('abc',"'",'$')
'abc$

Related

Add-Member to add a custom method to a PowerShell object

I want to add a custom method to an existing object. My problem is I may not find out how to make it accept parameters.
In this greatly simplified example I want to add a script block to a System.IO.FileInfo-Object to output a specific parameter to the screen:
$NewMethodScript = {
param(
[String] $Param1
)
write-host $this.$Param1
#Do lots of more stuff, call functions, etc...
}
$FInfo = [System.IO.FileInfo]::new("C:\File.txt")
$FInfo | Add-Member -MemberType ScriptMethod -Name NewMethod -Value $NewMethodScript
$FInfo.NewMethod "DirectoryName"

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

Powershell class implement get set property

How can I implement a get/set property with PowerShell class?
Please have a look on my example below:
Class TestObject
{
[DateTime]$StartTimestamp = (Get-Date)
[DateTime]$EndTimestamp = (Get-Date).AddHours(2)
[TimeSpan] $TotalDuration {
get {
return ($this.EndTimestamp - $this.StartTimestamp)
}
}
hidden [string] $_name = 'Andreas'
[string] $Name {
get {
return $this._name
}
set {
$this._name = $value
}
}
}
New-Object TestObject
You can use Add-Member ScriptProperty to achieve a kind of getter and setter:
class c {
hidden $_p = $($this | Add-Member ScriptProperty 'p' `
{
# get
"getter $($this._p)"
}`
{
# set
param ( $arg )
$this._p = "setter $arg"
}
)
}
Newing it up invokes the initializer for $_p which adds scriptproperty p:
PS C:\> $c = [c]::new()
And using property p yields the following:
PS C:\>$c.p = 'arg value'
PS C:\>$c.p
getter setter arg value
This technique has some pitfalls which are mostly related to how verbose and error-prone the Add-Member line is. To avoid those pitfalls, I implemented Accessor which you can find here.
Using Accessor instead of Add-Member does an amount of error-checking and simplifies the original class implementation to this:
class c {
hidden $_p = $(Accessor $this {
get {
"getter $($this._p)"
}
set {
param ( $arg )
$this._p = "setter $arg"
}
})
}
Here's how I went about it
[string]$BaseCodeSignUrl; # Getter defined in __class_init__. Declaration allows intellisense to pick up property
[string]$PostJobUrl; # Getter defined in __class_init__. Declaration allows intellisense to pick up property
[hashtable]$Headers; # Getter defined in __class_init__. Declaration allows intellisense to pick up property
[string]$ReqJobProgressUrl; # Getter defined in __class_init__. Declaration allows intellisense to pick up property
# Powershell lacks a way to add get/set properties. This is a workaround
hidden $__class_init__ = $(Invoke-Command -InputObject $this -NoNewScope -ScriptBlock {
$this | Add-Member -MemberType ScriptProperty -Name 'BaseCodeSignUrl' -Force -Value {
if ($this.Production) { [CodeSign]::CodeSignAPIUrl } else { [CodeSign]::CodeSignTestAPIUrl }
}
$this | Add-Member -MemberType ScriptProperty -Name 'PostJobUrl' -Force -Value {
"$($this.BaseCodeSignUrl)/Post?v=$([CodeSign]::ServiceApiVersion)"
}
$this | Add-Member -MemberType ScriptProperty -Name 'Headers' -Force -Value {
#{
_ExpireInMinutes=[CodeSign]::Timeout.Minutes;
_CodeSigningKey=$this.Key;
_JobId=$this.JobId;
_Debug=$this.Dbg;
_Token=$this.Token;
}
}
$this | Add-Member -MemberType ScriptProperty -Name 'ReqJobProgressUrl' -Force -Value {
"$($this.BaseCodeSignUrl)Get?jobId=$($this.JobId)"
}
});

How do I create an anonymous object in 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; }