Method .GetType() returns different values for the same variable - powershell

Practicing with the . GetType() method, I have found that if I write a function that returns 'Name' and 'BaseType' I get a different result than expected when I do not write the function.
Example:
function Get-BaseType_Name {
param(
[Parameter(Mandatory,ValuefromPipeline=$True)]
[System.Object]$var
)
$var.GetType() | Select -Property Name, BaseType;
}
$var = Get-Service
$var | Get-BaseType_Name
I get:
Name BaseType
---- --------
ServiceController System.ComponentModel.Component
However, if I execute the sentences:
$var = Get-Service
$var.GetType() | Select -Property Name, BaseType;
I get:
Name BaseType
---- --------
Object[] System.Array
Why is that?
I expected the same result in both cases

Per #MatthiasRJessen's answer, you're passing service objects into your function one-at-a-time via the pipeline.
There's a little bit more to it though - the reason you're only seeing one output result is because your function is equivalent this:
function Get-BaseType_Name {
param(
[Parameter(Mandatory,ValuefromPipeline=$True)]
[System.Object]$var
)
END {
#^^^^ END BLOCK
$var.GetType() | Select -Property Name, BaseType;
}
}
That is, your function body is being treated as an end block, which you can confirm if you look at the AST that PowerShell generates for your code:
${function:Get-BaseType_Name}.Ast.Body
which produces the following output.
(Note that the BeginBlock and ProcessBlock are empty and your code appears in the EndBlock).
Attributes : {}
UsingStatements : {}
ParamBlock : param(
[Parameter(Mandatory,ValuefromPipeline=$True)]
[System.Object]$var
)
BeginBlock :
^^^^^^^^^^ no definition
ProcessBlock :
^^^^^^^^^^^^ no definition
EndBlock : param(
[Parameter(Mandatory,ValuefromPipeline=$True)]
[System.Object]$var
)
$var.GetType() | Select -Property Name, BaseType
^^^^^^^^ your code is in this block
DynamicParamBlock :
ScriptRequirements :
Extent : {
param(
[Parameter(Mandatory,ValuefromPipeline=$True)]
[System.Object]$var
)
$var.GetType() | Select -Property Name, BaseType;
}
Parent : function Get-BaseType_Name {
param(
[Parameter(Mandatory,ValuefromPipeline=$True)]
[System.Object]$var
)
$var.GetType() | Select -Property Name, BaseType;
}
If you have a look at the end section in about_Functions_Advanced_Methods it says:
end
This block is used to provide optional one-time post-processing for the function.
What's happening is PowerShell is feeding each of the results from Get-Service into the Get-BaseType_Name function one-at-a-time, but your code is only actually doing anything once the last item has been processed, and in that case $var holds a reference to the last item, which you can see if you run this instead:
function Get-BaseType_Name {
param(
[Parameter(Mandatory,ValuefromPipeline=$True)]
[System.Object]$var
)
write-host $var.Name
#^^^^^^^^^^^^^^^^^^^ -- write the name of the service to the console
$var.GetType() | Select -Property Name, BaseType;
}
Get-Service | Get-BaseType_Name
On my machine I see this output:
XboxNetApiSvc
Name BaseType
---- --------
ServiceController System.ComponentModel.Component
where XboxNetApiSvc is the name of the last service returned by Get-Service.
If you want to see the output for every item you can put your code in the process block instead:
function Get-BaseType_Name {
param(
[Parameter(Mandatory,ValuefromPipeline=$True)]
[System.Object]$var
)
PROCESS {
#^^^^^^^^ PROCESS BLOCK
$var.GetType() | Select -Property Name, BaseType;
}
}
Get-Service | Get-BaseType_Name
and then your optput will look like this:
Name BaseType
---- --------
ServiceController System.ComponentModel.Component
ServiceController System.ComponentModel.Component
ServiceController System.ComponentModel.Component
... etc ...
In summary, you're sending a collection of service through the pipeline (per #MathiasRJessen's answer), but you're only returning values for the last item in that collection because PowerShell is implicitly treating your function body as an end block...

From the about_Pipelines help topic:
One-at-a-time processing
[...]
When you pipe multiple objects to a command, PowerShell sends the objects to the command one at a time. When you use a command parameter, the objects are sent as a single array object. This minor difference has significant consequences.
When executing a pipeline, PowerShell automatically enumerates any type that implements the IEnumerable interface and sends the members through the pipeline one at a time. The exception is [hashtable], which requires a call to the GetEnumerator() method.
In other words: this is by design - PowerShell sees that $var contains an array (a type that implements the IEnumerable interface), and starts enumerating the items in it one-by-one
You can prevent automatic enumeration by using Write-Output -NoEnumerate:
PS ~> Write-Output $var -NoEnumerate | Get-BaseType_Name
Name BaseType
---- --------
Object[] System.Array

Related

Weird Object[] casting from PSObject through a function call

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

Casting ScriptBlock as Action[T] for Generic.List[T] ForEach method [duplicate]

I'm confused what I'm doing wrong in ForEach method syntax of List?
PS D:\ntt> $nicInfo.IpConfigurations.Count
2
PS D:\ntt> $nicInfo.IpConfigurations[0]
PrivateIpAddressVersion Name Primary PrivateIpAddress PrivateIpAllocationMethod Subnet Name PublicIpAddress Name ProvisioningState
----------------------- ---- ------- ---------------- ------------------------- ----------- -------------------- -----------------
IPv4 ipconfig1 True 10.233.0.4 Dynamic Succeeded
PS D:\ntt> $nicInfo.IpConfigurations.GetType()
IsPublic IsSerial Name BaseType
-------- -------- ---- --------
True True List`1 System.Object
PS D:\ntt> $nicInfo.IpConfigurations.ForEach({$_})
PS D:\ntt>
The problem is that PowerShell's own .ForEach() collection method is preempted by the List<T> type's own .ForEach() method in this case:
PowerShell's own .ForEach({ ... }):
defines $_ as the input object at hand for the script-block argument ({ ... })
passes any output produced inside the script block through (to PowerShell's success output stream).
By contrast, List<T>'s .ForEach({ ... }) converts the script block to an Action<T> delegate, which has the following implications:
The delegate doesn't know about $_ inside the script block and instead receives a single argument that must be accessed as $args[0].
Output from the script block is ignored, because an Action<T> delegate by definition has no return value.
While you can produce host (console) output with Write-Host from within the script block, such output cannot be used programmatically, because it bypasses PowerShell's output streams and can therefore neither be captured nor redirected.
Tip of the hat to PetSerAl for providing the crucial pointers in comments.
Workarounds:
If the script block you pass to .ForEach() need not produce any output, all that's needed is to use $args[0] in lieu of $_ in your script block, though you may still opt to use one of the other workarounds below in order to avoid confusion.
If output is needed, the simplest solution is to convert the List<T> instance to an array with .ToArray() first, on which .ForEach() works as expected; a simplified example:
$list = [System.Collections.Generic.List[object]] ('foo', 'bar')
$list.ToArray().ForEach({ $_ + '!' }) # Note the .ToArray() call.
The above produces 'foo!', 'bar!', as expected.
Alternatively, you may use:
a foreach loop to process the list items, which means you must pick an iteration variable name and refer to that instead of $_ in the loop body; e.g.:
foreach ($itm in $list) { $itm + '!' }
or ForEach-Object in a pipeline (slower, but doesn't require changing the script block), as shown in No Refunds No Returns' answer; e.g.:
$list | ForEach-Object { $_ + '!' }
Are you trying to do something with each item in the collection? Do you want to do something like this:
$nicInfo.IpConfigurations | ForEach-Object {
$ipConfiguration = $_
write-Output $ipConfiguration
# do more stuff with this $ipConfiguration
}
Just for your reference, you can use this code for list.ForEach().
$nicInfo.IpConfigurations.ForEach({write-host $args[0].ToString()})
And I test it myself, it works. Sample code as below:
$s=New-Object System.Collections.Generic.List[string]
$s.Add("hello_1")
$s.Add("hello_2")
$s.Add("hello_3")
$s.ForEach({write-host $args[0].ToString()})
Test result as below:
As well as I found this similar issue, #PetSerAl explained very well there.

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

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

Confused Powershell returned array type

The following shows that the returned type are different depends on how many rows returned. Why it's designed this way? It's very easy to make assumption it always returns an array and write the code $a.Length $a | % { ....} which will raise error when it returns only one row, unless the it's written as $a = #(invoke-....), which is easy to forget.
$a=Invoke-Sqlcmd -ServerInstance server "select 1 a"
$b=Invoke-Sqlcmd -ServerInstance server "select 1 a union all select 2"
$a.GetType()
IsPublic IsSerial Name BaseType
-------- -------- ---- --------
True False DataRow System.Object
And the following statement returns an array of object (BTW, why not an array of DataRow?)
$b.GetType()
IsPublic IsSerial Name BaseType
-------- -------- ---- --------
True True Object[] System.Array
However, gm returns the same type for both variables. Why it's designed this way which can be very confused.
Question:
What's the point that the array is removed when only one item is returned?
Why gm get item type of an array? How to gm of an array?
Why getType() cannot return the data type of the item when it returns an array type?
?
PS SQLSERVER:\> $a|gm
TypeName: System.Data.DataRow
Name MemberType Definition
---- ---------- ----------
AcceptChanges Method void AcceptChanges()
......
ToString Method string ToString()
Item ParameterizedProperty System.Object Item(int columnIndex) {get;set;}, System.Object Item(string co...
a Property int a {get;set;}
PS SQLSERVER:\> $b|gm
TypeName: System.Data.DataRow
Name MemberType Definition
---- ---------- ----------
AcceptChanges Method void AcceptChanges()
......
ToString Method string ToString()
Item ParameterizedProperty System.Object Item(int columnIndex) {get;set;}, System.Object Item(string co...
a Property int a {get;set;}
Most of the time in PowerShell, functions/cmdlets that return objects, simply return the object. If more than 1 object is to be returned, the function just keeps returning objects until it's done. PowerShell handles all of the returned objects and gives you an array.
gm is an alias for Get-Member. When you call it by piping to it $a | gm, you are invoking it as a pipeline. In this case, each object in $a is individually passed to Get-Member, so it returns the type of the individual object(s). If they are all the same, then it will only display it once, but it's actually checking all of them. You can prevent this by calling Get-Member -InputObject $a which should show you the array type if it is an array.
Similar to the above, .GetType() gets the type of whatever object it's invoked on, so if it's an array, then it returns that; it's not looking at the individual elements.
I also want to point out that % (ForEach-Object) and foreach() work fine when not used on an array: "hello" | % { $_ }, as does foreach($msg in "hello") { $msg }.
To address the issue of $a.Length not working when the return value is a single object, it depends on your powershell version.
In version 2, you will see the behavior you are seeing: you'll have to wrap it in an array first, or test for an array with something like:
if ($a -is [Array]) {
$itemCount = $a.Length
} else {
$itemCount = 1
}
But, in powershell 3+, you can do $a.Length even if $a is just some object and not an array. It will return 1. Length may not show up in autocomplete or in intellisense, but it will work.

Printing object properties in Powershell

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