Replace "," in PSCustomObject's properties while retaining the object type - powershell

I have the following sample code which replaces all comma values (,) with a period (.):
$foo = [PSCustomObject]#{
Num1 = "0.11"
Num2 = "0,12"
}
Write-Host "Type before:" $foo.GetType().FullName
$foo = $foo -replace(",",".")
Write-Host "Type after:" $foo.GetType().FullName
It produces the following output:
Type before: System.Management.Automation.PSCustomObject
Type after: System.String
I'd like to retain the PSCustomObject type and not have it converted to a string. Any ideas on how to accomplish this aside from:
$foo.Num2.Replace(",",".")
I'd prefer not to get into listing out each property with a replace statement as my real code has MANY properties in this object.

PowerShell comparison operators (including the replacement operator) implicitly convert the first operand to a type that matches the second operand. In your case that is transoforming the custom object to a string representation of itself.
To replace something in the properties of your object: enumerate the properties and do the replacement operation on the actual properties, not the object as a whole.
$foo.PSObject.Properties | ForEach-Object {
if ($_.Value -is [string]) {
$_.Value = $_.Value.Replace(',', '.')
}
}

Related

How come I can create objects with [pscustomobject] but not [System.Management.Automation.PSCustomObject]?

How come I can create objects with the type accelerator [pscustomobject] but not its fullname, [System.Management.Automation.PSCustomObject]? Is there some magical constructor I'm not accessing?
$a = [pscustomobject]#{name='joe'}
$a.gettype().fullname
System.Management.Automation.PSCustomObject
[System.Management.Automation.PSCustomObject]#{name='joe'}
InvalidArgument: Cannot convert the "System.Collections.Hashtable" value of type "System.Collections.Hashtable" to type "System.Management.Automation.PSCustomObject".
Or I can try [System.Management.Automation.PSObject], but I just get a hashtable:
[psobject].Assembly.GetType('System.Management.Automation.TypeAccelerators')::get.pscustomobject.fullname
System.Management.Automation.PSObject
[System.Management.Automation.PSObject]#{name='joe'}
Name Value
---- -----
name joe
Inspired by this thread: https://powershell.org/forums/topic/type-accelerator
Is there some magical constructor I'm not accessing?
No, there's magic sauce in the compiler - whenever the PowerShell compiler sees a cast expression where the right-hand side is a dictionary and the type literal has the exact name pscustomobject, it'll treat the dictionary or hashtable (whether literal or not) as an ordered dictionary and convert it to a PSObject.
From VisitConvertExpression in Compiler.cs:
var typeName = convertExpressionAst.Type.TypeName;
var hashTableAst = convertExpressionAst.Child as HashtableAst;
Expression childExpr = null;
if (hashTableAst != null)
{
var temp = NewTemp(typeof(OrderedDictionary), "orderedDictionary");
if (typeName.FullName.Equals(LanguagePrimitives.OrderedAttribute, StringComparison.OrdinalIgnoreCase))
{
return Expression.Block(
typeof(OrderedDictionary),
new[] { temp },
BuildHashtable(hashTableAst.KeyValuePairs, temp, ordered: true));
}
if (typeName.FullName.Equals("PSCustomObject", StringComparison.OrdinalIgnoreCase))
{
// pure laziness here - we should construct the PSObject directly. Instead, we're relying on the conversion
// to create the PSObject from an OrderedDictionary.
childExpr = Expression.Block(
typeof(OrderedDictionary),
new[] { temp },
BuildHashtable(hashTableAst.KeyValuePairs, temp, ordered: true));
}
}
Notice how this is also how [ordered]#{Key='Value'} results in an OrderedDictionary

How do I force a function to return a single element array instead of the contained object?

I have a function (actually several instances of this), but there are times that it may return a list of several elements, and there are times that it may return a single element. I want the function to return an array ([System.Object[]]) every time so that (on the receiving end), I can always anticipate it being an array and index into it, even if I am just pulling the 0th element.
I've tried casting the return type multiple ways (see code below) ... including (for example) return #("asdf"), return [System.Object[]]#("asdf") and similar, but it seems that the only to get a consistent behavior is to add a second null element in the array ... which feels wrong to me. (See code below)
function fn1 {
return #("asdf")
}
function fn2 {
return [array]#("asdf")
}
function fn3 {
return [System.Object[]]#("asdf")
}
function fn4 {
# This works but with the side effect of sending a null string that is not actually necessary
return #("asdf",$Null)
}
$v = fn1 # Same for fn2, fn3.
$v.GetType().Name # Expected: Object[], Actual: String
$v[0] # Expected: "asdf", Actual: "a"
$v = fn4
$v.GetType().Name # Expected: Object[], Actual: Object[]
$v[0] # Expected: "asdf", Actual: "asdf"
If I understand your question, you can use the , operator when returning the value; e.g.:
function fn1 {
,#("asdf")
}
The function will output a single-element array.
As an alternative to wrapping in an extra array, use Write-Output -NoEnumerate:
function fn1 {
Write-Output #('asdf') -NoEnumerate
}
or, in cmdlet-bound/advanced functions prior to version 4.0:
function fn1 {
[CmdletBinding()]
param()
$PSCmdlet.WriteObject(#('asdf'), $false)
}

Passing an ordered hashtable to a function

How can I go about passing an ordered hashtable to a function?
The following throws an error:
The ordered attribute can be specified only on a hash literal node.
function doStuff {
Param (
[ordered]$theOrderedHashtable
)
$theOrderedHashtable
}
$datFileWithMinSizes = [ordered]#{"FileA.DAT" = "4"; "FileB.DAT" = "5"; "FileC.DAT" = "91" ; "FileD.DAT" = "847" }
doStuff -theOrderedHashtable $datFileWithMinSizes
The following does not maintain the correct order:
function doStuff {
Param (
[Hashtable]$theOrderedHashtable = [ordered]#{}
)
$theOrderedHashtable
}
$datFileWithMinSizes = [ordered]#{"FileA.DAT" = "4"; "FileB.DAT" = "5"; "FileC.DAT" = "91" ; "FileD.DAT" = "847" }
doStuff -theOrderedHashtable $datFileWithMinSizes
The only way I can currently get this to work is by not specifying the type as follows, but I want to specify the type:
function doStuff {
Param (
$theOrderedHashtable
)
$theOrderedHashtable
}
$datFileWithMinSizes = [ordered]#{"FileA.DAT" = "4"; "FileB.DAT" = "5"; "FileC.DAT" = "91" ; "FileD.DAT" = "847" }
doStuff -theOrderedHashtable $datFileWithMinSizes
Mathias is right, but I wanted to point out that there is a way to accept both types without using parameter sets.
Both types implement the IDictionary interface, so you can strongly type your parameter with the interface instead, and then any type (including custom types that you create or don't know about yet) which implements the interface will be accepted:
function Do-Stuff {
[CmdletBinding(DefaultParameterSetName='Ordered')]
param(
[Parameter(Mandatory=$true,Position=0,ParameterSetName='Ordered')]
[System.Collections.IDictionary]$Dictionary
)
$Dictionary.GetType().FullName
}
This would accept both:
C:\WINDOWS\system32\WindowsPowerShell\v1.0> do-stuff #{}
System.Collections.Hashtable
C:\WINDOWS\system32\WindowsPowerShell\v1.0> do-stuff ([ordered]#{})
System.Collections.Specialized.OrderedDictionary
Similarly, if you want to only accept an ordered dictionary (but not just the specific OrderedDictionary type), you can use the IOrderedDictionary interface, which is implemented by the aforementioned type, but not by [hashtable]:
function Do-Stuff {
[CmdletBinding(DefaultParameterSetName='Ordered')]
param(
[Parameter(Mandatory=$true,Position=0,ParameterSetName='Ordered')]
[System.Collections.Specialized.IOrderedDictionary]$Dictionary
)
$Dictionary.GetType().FullName
}
Then:
C:\WINDOWS\system32\WindowsPowerShell\v1.0> do-stuff ([ordered]#{})
System.Collections.Specialized.OrderedDictionary
C:\WINDOWS\system32\WindowsPowerShell\v1.0> do-stuff #{}
Do-Stuff : Cannot process argument transformation on parameter 'Dictionary'. Cannot convert the "System.Collections.Hashtable" value of type "System.Collections.Hashtable" to type
"System.Collections.Specialized.IOrderedDictionary".
At line:1 char:10
+ do-stuff #{}
+ ~~~
+ CategoryInfo : InvalidData: (:) [Do-Stuff], ParameterBindingArgumentTransformationException
+ FullyQualifiedErrorId : ParameterArgumentTransformationError,Do-Stuff
Use the full type name:
function Do-Stuff {
param(
[System.Collections.Specialized.OrderedDictionary]$OrderedHashtable
)
$OrderedHashtable
}
To support both regular hashtables and ordered dictionaries, you'll have to use separate parameter sets: use the [System.Collections.IDictionary] interface, as suggested by briantist
function Do-Stuff {
[CmdletBinding(DefaultParameterSetName='Ordered')]
param(
[Parameter(Mandatory=$true,Position=0,ParameterSetName='Ordered')]
[System.Collections.Specialized.OrderedDictionary]$OrderedHashtable,
[Parameter(Mandatory=$true,Position=0,ParameterSetName='Hashtable')]
[hashtable]$Hashtable
)
if($PSCmdlet.ParameterSetName -eq 'Hashtable'){
$OrderedHashtable = $Hashtable
}
$OrderedHashtable
}
Just to complement the existing, helpful answers:
What the error message
The ordered attribute can be specified only on a hash literal node.
is trying to tell you:
[ordered] is syntactic sugar, and it only works before hashtable literals (#{ ... }).
You can determine the actual type of an ordered hashtable literal as follows:
PS> ([ordered] #{ foo = 1 }).GetType().FullName
System.Collections.Specialized.OrderedDictionary
That is, an ordered hashtable literal in PowerShell is an instance of type [System.Collections.Specialized.OrderedDictionary].

PowerShell class array of strings syntax

I have the following class, but instead of a [String] member variable I need to declare an array of Strings. What's the syntax?
class SomeClass{
[String] $patterns;
SomeClass(){
$this.patterns = "Maintenance Code","Page \d+ of";
}
[Bool]SomeMethod([string]$txt){}
}
So as PetSerAl said [System.String[]] but [String[]] is just fine.
It's also mentioned in help about_arrays.
To create a strongly typed array, that is, an array that can contain only
values of a particular type, cast the variable as an array type, such
as string[], long[], or int32[]. To cast an array, precede the variable
name with an array type enclosed in brackets. For example, to create a
32-bit integer array named $ia containing four integers (1500, 2230, 3350,
and 4000), type:
[int32[]]$ia = 1500,2230,3350,4000

Boolean variable gets returned as an Object[]

I have a function which is returning a boolean variable
function test ($param1, $param2)
{
[boolean]$match=$false;
<# function logic #>
return $match
}
when I try and catch the function call in a variable $retByFunct=testing $param1 $param 2
I am getting $retByFunct as an Object Array. If I try and force the $retByFunct to be a boolean variable i.e. [boolean] $retByFunct=testing $param1 $param 2, I get the following error.
Cannot convert value "System.Object[]" to type System.Boolean
I checked out $match.GetType() just before returning it. The console says its a boolean, so am confused as to why after function call its getting converted to an Object Array.
I am aware this happens for some collection objects and there is a work around for that, but how do I handle a case for a variable?
As #mjolinor said, you need to show the rest of your code. I suspect it's generating output on the success output stream somewhere. PowerShell functions return all output on that stream, not just the argument of the return keyword. From the documentation:
In PowerShell, the results of each statement are returned as output, even without a statement that contains the Return keyword. Languages like C or C# return only the value or values that are specified by the return keyword.
There's no difference between
function Foo {
'foo'
}
or
function Foo {
'foo'
return
}
or
function Foo {
return 'foo'
}
Each of the above functions returns the string foo when called.
Additional output causes the returned value to be an array, e.g.:
function Foo {
$v = 'bar'
Write-Output 'foo'
return $v
}
This function returns an array 'foo', 'bar'.
You can suppress undesired output by redirecting it to $null:
function Foo {
$v = 'bar'
Write-Output 'foo' | Out-Null
Write-Output 'foo' >$null
return $v
}
or by capturing it in a variable:
function Foo {
$v = 'bar'
$captured = Write-Output 'foo'
return $v
}
return something ## is not the only thing that PowerShell returns. This is one of the gotchas of PowerShell, if you will. All output on the success output stream will also be returned. that's why you have an array of objects returning from your function.
function test {
"hello"
return "world"
}
$mytest=test
$mytest
try the code above... you will not get just "world" but
"hello"
"world"
$mytest.count
2