I've been working on some PowerShell functions to manage objects implemented in an assembly we have created. One of the classes I have been working with implements IEnumerable. Unfortunatly, this causes PowerShell to unroll the object at every opportunity. (I can't change the fact that the class implements IEnumerable.)
I've worked around the problem by creating a PSObject and copying the properties of our custom object to the PSObject, then returning that instead of the custom object. But I'd really rather return our custom object.
Is there some way, presumably using my types.ps1xml file, to hide the GetEnumerator() method of this class from PowerShell (or otherwise tell PowerShell to never unroll it).
Wrapping in a PSObject is probably the best way.
You could also explicitly wrap it in another collection—PowerShell only unwraps one level.
Also when writing a cmdlet in C#/VB/... when you call WriteObject use the overload that takes a second parameter: if false then PowerShell will not enumerate the object passed as the first parameter.
Check out the Write-Output replacement http://poshcode.org/2300 which has a -AsCollection parameter that lets you avoid unrolling. But basically, if you're writing a function that outputs a collection, and you don't want that collection unrolled, you need to use CmdletBinding and PSCmdlet:
function Get-Array {
[CmdletBinding()]
Param([Switch]$AsCollection)
[String[]]$data = "one","two","three","four"
if($AsCollection) {
$PSCmdlet.WriteObject($data,$false)
} else {
Write-Output $data
}
}
If you call that with -AsCollection you'll get very different results, although they'll LOOK THE SAME in the console.
C:\PS> Get-Array
one
two
three
four
C:\PS> Get-Array -AsCollection
one
two
three
four
C:\PS> Get-Array -AsCollection| % { $_.GetType() }
IsPublic IsSerial Name BaseType
-------- -------- ---- --------
True True String[] System.Array
C:\PS> Get-Array | % { $_.GetType() }
IsPublic IsSerial Name BaseType
-------- -------- ---- --------
True True String System.Object
True True String System.Object
True True String System.Object
True True String System.Object
This may not directly answer the original question, since it technically isn't returning the custom object itself, but I had a similar problem where I was trying to display the properties of a custom type that implements IEnumerable and thought it was worth sharing. Piping this object into cmdlets like Get-Member and Select-Object always enumerates the contents of the collection. However, using those cmdlets directly and passing the object to the -InputObject parameter produces the expected results. Here, I'm using an ArrayList as an example, but $list can be an instance of your custom collection type:
# Get a reference to a collection instance
$list = [System.Collections.ArrayList]#(1,2,3)
# Display the properties of the collection (in this case, the ArrayList),
# not the items it contains (Int32)
Select-Object -InputObject $list -Property *
# List the members of the ArrayList class, not the contained items
Get-Member -InputObject $list
As other comments have mentioned, you can save the results of Select-Object as a variable, creating a new PSObject that you can use directly without enumerating the contents:
# Wrap the collection in a PSObject
$obj = Select-Object -InputObject $list -Property *
# Properties are displayed without enumerating contents
$obj
$obj | Format-List
$obj | Format-Table
Related
I'm currently coding a function able to cast an ADO (EML attachments from Email) into a PSObject. A stub of the code looks like this:
function Get-OriginalMailAttributes([__ComObject]$email){
#.DESCRIPTION Downloads in Temp the attachment and open it.
# Returns PSObjectwith values.
#STEP 1: Download EML
$TEMPFILE = "..."
if($email.Attachments.Count){
$attachment=$email.Attachments|?{$_.Filename.endsWith(".eml")} | select -First 1
$fileName = $TEMPFILE + $(New-Guid | select -exp Guid) + ".eml"
$attachment.SaveAsFile($fileName)
#STEP2 : Retrieve EML Objects
$adoDbStream = New-Object -ComObject ADODB.Stream
$adoDbStream.Open()
$adoDbStream.LoadFromFile($fileName)
$cdoMessage = New-Object -ComObject CDO.Message
$cdoMessage.DataSource.OpenObject($adoDbStream, "_Stream")
#STEP 3: Bind values
$attributes = New-Object PSObject -Property #{
From = [string]($cdoMessage.Fields.Item("urn:schemas:mailheader:from").Value.toString())
}
#STEP 4: Cleanup
[System.Runtime.InteropServices.Marshal]::ReleaseComObject($cdoMessage)
[System.Runtime.InteropServices.Marshal]::ReleaseComObject($adoDbStream)
Remove-Item $fileName
return $attributes
# Note that the debugger acknowledge the fact that $attributes is PSObject.
}
}
I'm then calling the function at some point:
$sender_raw = Get-OriginalMailAttributes $email
$sender_raw.getType()
IsPublic IsSerial Name BaseType
-------- -------- ---- --------
True True Object[] System.Array
$sender_raw
0
0
From
----
"Bob" <dummy#contoso.com>
$sender_raw.From # Crashes.
Where does this behavior come from?
Typically when you have "extra" values in the output from a function it's because you're calling methods that have return values and not dealing with those values.
.Add() methods are notorious for outputting the index of the item that was added.
To avoid this problem, you have a few options:
Cast to void: [void]$collection.Add($item)
Assign to $null: $null=$collection.Add($item)
Pipe to out-null: $collection.Add($item) | out-null
I don't see any obvious methods in your code, but adding Write-output 'Before Step 1' etc. throughout the code should make it clear where the offending statements are.
This weird behavior comes from multi-returns from PowerShell functions.
PS> function f() {
1+1
return 4
}
PS> f
2
4
In short, if one-liners are returning values, they are added as an Object[] array to the output.
While this is a particularly chaotic way to handle return values of a function, my solution on my code will be to redirect output NULL:
stub...
[System.Runtime.InteropServices.Marshal]::ReleaseComObject($cdoMessage) | Out-Null
[System.Runtime.InteropServices.Marshal]::ReleaseComObject($adoDbStream) | Out-Null
See Suppressing return values in PowerShell functions
I am making a PSObject from a json file
bar.json
{
"derp": {
"buzz": 42
},
"woot": {
"toot": 9000
}
}
I can make a PSCustomObject form the json using ConvertFrom-Json
$foo = Get-Content .\bar.json -Raw |ConvertFrom-Json
$foo.gettype()
IsPublic IsSerial Name BaseType
-------- -------- ---- --------
True False PSCustomObject System.Object
However if I try and splat multiple json files, I get an array
$foo = Get-Content .\*.json -Raw |ConvertFrom-Json
$foo.gettype()
IsPublic IsSerial Name BaseType
-------- -------- ---- --------
True False Object[] System.Array
In order to iterate over $foo, I need 2 different code paths depending on the object type.
Can I get a single object from multiple json files?
If not, how would I compress an array of objects into a single object?
I've tried to make a new object $bar that contains all the array items of $foo
$bar = new-object psobject
$bar | add-member -name $foo[0].psobject.properties.name -value $foo[0].'derp' -memberType NoteProperty
Update
Per Walter Mitty's request. If I load a single file and run $foo[0]
$foo = Get-Content .\bar.json -Raw |ConvertFrom-Json
$foo[0].gettype()
IsPublic IsSerial Name BaseType
-------- -------- ---- --------
True False PSCustomObject System.Object
$foo[0]
derp woot
------------ ------------
#{Provisioners=System.Object[]; OS=windows; Size=; V... #{Provisioners=System.Object[]; OS=windows; Size=; V...
Solution
I initially implemented AP's answer, but later refactored it to use mklement0 answer.
While $allObjects is an array, it still allows me to reference values by name which is what I was looking for
$allObjects = #(
Get-ChildItem '.\foo' -Filter *.json -Recurse | Get-Content -Raw | ConvertFrom-Json
)
# iterating over nested objects inside an array is hard.
# make it easier by moving all array objects into 1 parent object where
# the 'key' is the name (opposed to AP's answer where filename = key)
$newObject = New-Object PSObject
foreach ($i in $allObjects) {
$i.psobject.members | ?{$_.Membertype -eq 'noteproperty'} |%{$newObject | add-member $_.Name $_.Value}
}
If all you want is an Array of JSON objects which are parsed from the different files:
$final = #{}
# Loop Thru All JSON Files, and add to $Final[<name>] = <JSON>
ls .\*.json | % {
$o = cat $_ -Raw | ConvertFrom-Json
$final[$_.name] = [PsCustomObject]$o
}
$final = [PsCustomObject]$final
This will produce a Name -> Data map for all your JSON files as a nested PSObject
AP.'s helpful answer is an elegant solution for creating a single top-level object ([pscustomobject] instance) that:
houses all JSON-converted objects as properties named for their input filenames.
E.g., $final would contain a bar.json property whose value is the object equivalent of the JSON string from the question.
A caveat is that with duplicate filenames the last file processed would "win".
See bottom for a usage note re Unix-style aliases ls and cat.
To get an array of all converted-from-JSON objects, the following is sufficient:
$allObjects = #(Get-ChildItem -Filter *.json | Get-Content -Raw | ConvertFrom-Json)
Note that if you only need a single directory's JSON files,
$allObjects = #(Get-Content -Path *.json -Raw | ConvertFrom-Json) works too.
Note the use of #(...), the array subexpression operator, which ensures that what is returned is treated as an array, even if only a single object is returned.
By default, or when you use $(...), the regular subexpression operator, PowerShell unwraps any (potentially nested) single-item collection and returns only the item itself; see Get-Help about_Operators.
A note on the use of Unix-style aliases ls and cat for PowerShell's Get-ChildItem and Get-Content cmdlets, respectively:
Now that PowerShell is cross-platform, these aliases only exist in the Windows edition of PowerShell, whereas a decision was made to omit them from PowerShell Core on Unix platforms, so as not to shadow the standard Unix utilities of the same name.
It is better to get into the habit of using PowerShell's own aliases, which follow prescribed naming conventions and do not conflict with platform-specific utilities.
E.g., gci can be used for Get-ChildItem, and gc for Get-Content
For the naming conventions behind PowerShell's own aliases, see the documentation.
In PowerShell v3.0 PSCustomObject was introduced. It's like PSObject, but better. Among other improvements (e.g. property order being preserved), creating object from hashtable is simplified:
[PSCustomObject]#{one=1; two=2;}
Now it seems obvious that this statement:
[System.Management.Automation.PSCustomObject]#{one=1; two=2;}
would work the same way, because PSCustomObject is an "alias" for full namespace + class name. Instead I get an error:
Cannot convert the "System.Collections.Hashtable" value of type "System.Collections.Hashtable" to type "System.Management.Automation.PSCustomObject".
I listed accelerators for both types of objects:
[accelerators]::get.GetEnumerator() | where key -Like ps*object
Key Value
--- -----
psobject System.Management.Automation.PSObject
pscustomobject System.Management.Automation.PSObject
and discovered that both reference the same PSObject class - this has to mean that using accelerators can do a bunch of other stuff than just making the code shorter.
My questions regarding this issue are:
Do you have some interesting examaples of differences between using an accelerator vs using full type name?
Should using full type name be avoided whenever an accelerator is available as a general best practice?
How to check, maybe using reflection, if an accelerator does other stuff than just pointing to underlying class?
Looking at the static methods:
PS C:\> [PSCustomObject] | gm -Static -MemberType Method
TypeName: System.Management.Automation.PSObject
Name MemberType Definition
---- ---------- ----------
AsPSObject Method static psobject AsPSObject(System.Object obj)
Equals Method static bool Equals(System.Object objA, System.Object objB)
new Method psobject new(), psobject new(System.Object obj)
ReferenceEquals Method static bool ReferenceEquals(System.Object objA, System.Object o...
PS C:\> [System.Management.Automation.PSCustomObject] | gm -Static -MemberType Method
TypeName: System.Management.Automation.PSCustomObject
Name MemberType Definition
---- ---------- ----------
Equals Method static bool Equals(System.Object objA, System.Object objB)
ReferenceEquals Method static bool ReferenceEquals(System.Object objA, System.Object o...
The type accelerator has a couple of new static methods added. I suspect it's using one of those as the constructor.
[PSObject] and [PSCustomObject] are aliases for the same type - System.Management.Automation.PSObject. I can't say there's a good reason for it, but it is at least suggestive of two different purposes, and maybe that's reason enough.
System.Management.Automation.PSObject is used to wrap objects. It was introduced to provide a common reflection api over any object that PowerShell wraps - .Net, WMI, COM, ADSI, or simple property bags.
System.Management.Automation.PSCustomObject is just an implementation detail. When you create a PSObject, the PSObject must wrap something. For property bags, the object wrapped is System.Management.Automation.PSCustomObject.SelfInstance (an internal member.) This instance is hidden from normal use of PowerShell, the only way to observe it is with reflection.
Property bags are created in multiple ways in PowerShell:
$o1 = [pscustomobject]#{Prop1 = 42}
$o2 = new-object psobject -Property #{Prop1 = 42 }
Both $o1 and $o2 above will be an instance of PSObject, and the PSObject will wrap PSCustomObject.SelfInstance. PSCustomObject.SelfInstance is used internally in PowerShell to tell the difference between a simple property bag and wrapping any other object.
When working in the interactive console if I define a new object and assign some property values to it like this:
$obj = New-Object System.String
$obj | Add-Member NoteProperty SomeProperty "Test"
Then when I type the name of my variable into the interactive window Powershell gives me a summary of the object properties and values:
PS C:\demo> $obj
SomeProperty
------------
Test
I basically want to do just this but from within a function in a script. The function creates an object and sets some property values and I want it to print out a summary of the object values to the Powershell window before returning. I tried using Write-Host within the function:
Write-Host $obj
But this just output the type of the object not the summary:
System.Object
How can I have my function output a summary of the object's property values to the Powershell window?
Try this:
Write-Host ($obj | Format-Table | Out-String)
or
Write-Host ($obj | Format-List | Out-String)
My solution to this problem was to use the $() sub-expression block.
Add-Type -Language CSharp #"
public class Thing{
public string Name;
}
"#;
$x = New-Object Thing
$x.Name = "Bill"
Write-Output "My name is $($x.Name)"
Write-Output "This won't work right: $x.Name"
Gives:
My name is Bill
This won't work right: Thing.Name
To print out object's properties and values in Powershell. Below examples work well for me.
$pool = Get-Item "IIS:\AppPools.NET v4.5"
$pool | Get-Member
TypeName: Microsoft.IIs.PowerShell.Framework.ConfigurationElement#system.applicationHost/applicationPools#add
Name MemberType Definition
---- ---------- ----------
Recycle CodeMethod void Recycle()
Start CodeMethod void Start()
Stop CodeMethod void Stop()
applicationPoolSid CodeProperty Microsoft.IIs.PowerShell.Framework.CodeProperty
state CodeProperty Microsoft.IIs.PowerShell.Framework.CodeProperty
ClearLocalData Method void ClearLocalData()
Copy Method void Copy(Microsoft.IIs.PowerShell.Framework.ConfigurationElement ...
Delete Method void Delete()
...
$pool | Select-Object -Property * # You can omit -Property
name : .NET v4.5
queueLength : 1000
autoStart : True
enable32BitAppOnWin64 : False
managedRuntimeVersion : v4.0
managedRuntimeLoader : webengine4.dll
enableConfigurationOverride : True
managedPipelineMode : Integrated
CLRConfigFile :
passAnonymousToken : True
startMode : OnDemand
state : Started
applicationPoolSid : S-1-5-82-271721585-897601226-2024613209-625570482-296978595
processModel : Microsoft.IIs.PowerShell.Framework.ConfigurationElement
...
Tip #1
Never use Write-Host.
Tip #12
The correct way to output information from a PowerShell cmdlet or function is to create an object that contains your data, and then to write that object to the pipeline by using Write-Output.
-Don Jones: PowerShell Master
Ideally your script would create your objects ($obj = New-Object -TypeName psobject -Property #{'SomeProperty'='Test'}) then just do a Write-Output $objects. You would pipe the output to Format-Table.
PS C:\> Run-MyScript.ps1 | Format-Table
They should really call PowerShell PowerObjectandPipingShell.
Some general notes.
$obj | Select-Object ⊆ $obj | Select-Object -Property *
The latter will show all non-intrinsic, non-compiler-generated properties. The former does not appear to (always) show all Property types (in my tests, it does appear to show the CodeProperty MemberType consistently though -- no guarantees here).
Some switches to be aware of for Get-Member
Get-Member does not get static members by default. You also cannot (directly) get them along with the non-static members. That is, using the switch causes only static members to be returned:
PS Y:\Power> $obj | Get-Member -Static
TypeName: System.IsFire.TurnUpProtocol
Name MemberType Definition
---- ---------- ----------
Equals Method static bool Equals(System.Object objA, System.Object objB)
...
Use the -Force.
The Get-Member command uses the Force parameter to add the intrinsic members and compiler-generated members of the objects to the display. Get-Member gets these members, but it hides them by default.
PS Y:\Power> $obj | Get-Member -Static
TypeName: System.IsFire.TurnUpProtocol
Name MemberType Definition
---- ---------- ----------
...
pstypenames CodeProperty System.Collections.ObjectModel.Collection...
psadapted MemberSet psadapted {AccessRightType, AccessRuleType,...
...
Use ConvertTo-Json for depth and readable "serialization"
I do not necessary recommend saving objects using JSON (use Export-Clixml instead).
However, you can get a more or less readable output from ConvertTo-Json, which also allows you to specify depth.
Note that not specifying Depth implies -Depth 2
PS Y:\Power> ConvertTo-Json $obj -Depth 1
{
"AllowSystemOverload": true,
"AllowLifeToGetInTheWay": false,
"CantAnyMore": true,
"LastResortOnly": true,
...
And if you aren't planning to read it you can -Compress it (i.e. strip whitespace)
PS Y:\Power> ConvertTo-Json $obj -Depth 420 -Compress
Use -InputObject if you can (and are willing)
99.9% of the time when using PowerShell: either the performance won't matter, or you don't care about the performance. However, it should be noted that avoiding the pipe when you don't need it can save some overhead and add some speed (piping, in general, is not super-efficient).
That is, if you all you have is a single $obj handy for printing (and aren't too lazy like me sometimes to type out -InputObject):
# select is aliased (hardcoded) to Select-Object
PS Y:\Power> select -Property * -InputObject $obj
# gm is aliased (hardcoded) to Get-Member
PS Y:\Power> gm -Force -InputObject $obj
Caveat for Get-Member -InputObject:
If $obj is a collection (e.g. System.Object[]), You end up getting information about the collection object itself:
PS Y:\Power> gm -InputObject $obj,$obj2
TypeName: System.Object[]
Name MemberType Definition
---- ---------- ----------
Count AliasProperty Count = Length
...
If you want to Get-Member for each TypeName in the collection (N.B. for each TypeName, not for each object--a collection of N objects with all the same TypeName will only print 1 table for that TypeName, not N tables for each object)......just stick with piping it in directly.
The below worked really good for me. I patched together all the above answers plus read about displaying object properties in the following link and came up with the below
short read about printing objects
add the following text to a file named print_object.ps1:
$date = New-Object System.DateTime
Write-Output $date | Get-Member
Write-Output $date | Select-Object -Property *
open powershell command prompt, go to the directory where that file exists and type the following:
powershell -ExecutionPolicy ByPass -File is_port_in_use.ps1 -Elevated
Just substitute 'System.DateTime' with whatever object you wanted to print. If the object is null, nothing will print out.
# Json to object
$obj = $obj | ConvertFrom-Json
Write-host $obj.PropertyName
Most of out of the box PowerShell commands are returning "complex" objects.
for example, Get-Process returns an array of System.Diagnostics.Process.
All of the function I've ever wrote was returning either a simple type, or an existing kind of object.
If I want to return a home made objects, what are guidelines ?
For example, imagine I have an object with these attributes: Name, Age. In C# I would have wrote
public class Person {
public string Name { get; set; }
public uint Age { get; set; }
}
What are my options for returning such object from a PowerShell function ?
As my goal is to create PowerShell module, should I move to a c# module instead of a ps1 file ?
should I compile such objects in a custom DLL and reference it in my module using LoadWithPartialName ?
Should I put my class in a powershell string, then dynamically compile it ?
you can use this syntax:
New-Object PSObject -Property #{
Name = 'Bob'
Age = 32
}
There are a few options for creating your own object with custom properties. It is also possible to extend existing objects with additional properties. The most explicit method is to use the Add-Member cmdlet:
$test = New-Object PSCustomObject
Add-Member -InputObject $test -Name Name -Value 'Bob' -MemberType NoteProperty
Add-Member -InputObject $test -Name Age -Value 32 -MemberType NoteProperty
You can also use the fact that objects can be extended combined with the fact that Select-Object can implicitly add properties to objects in the pipeline:
$test = '' | Select #{n='Name'; e={'Bob'}}, #{n='Age'; e={32}}
Personally I prefer the first method since it is more explicit and less hand waving magic, but at the end of the day it's a scripting language so whatever works is the right way to do it.