Process input [string] line-by-line in PowerShell - powershell

I am writing a function to convert a list of IP addresses from their dotted-decimal form to an unsigned integer in PowerShell. Here is my function:
function Convert-IPtoInteger() {
param(
[Parameter(Mandatory=$true, Position=0, ParameterSetName="IP Address",
ValueFromPipeline=$true, ValueFromPipelineByPropertyName=$true)]
[string] $addresses
)
process {
foreach ($ip in $addresses) {
if ($ip -match '(\d\d?\d?\.){3}\d\d?\d?') { # approximate regex, matches some impossible addresses like 999.0.0.0
$s = "$ip".Split(".");
[uint32] $x = 0;
(0..3) | ForEach-Object { # positions 0, 1, 2, 3 in previous string
$x = $x -shl 8; # bit shift previous value to the left by 8 bits
$x += $s[$_];
}
Write-Output $x;
}
}
}
}
I had tried this with $addresses declared as a scalar as shown and as a [string[]] array. In both cases, piping a string of multiple lines (created with shift-enter) causes an error after the first element. If I use the Get-Content command to read the same text from a file the program completes as expected.
PS C:\...\1> $example = "192.168.1.1
192.168.2.1"
PS C:\...\1> $example | Convert-IPtoInteger
Cannot convert value "1
192" to type "System.UInt32". Error: "Input string was not in a correct format."
At C:\...\.ps1:16 char:18
+ $x += $s[$_];
+ ~~~~~~~~~~~~
+ CategoryInfo : InvalidArgument: (:) [], RuntimeException
+ FullyQualifiedErrorId : InvalidCastFromStringToInteger
3232235776
PS C:\...\1> $example > example.txt
PS C:\...\1> Get-Content .\example.txt | Convert-IPtoInteger
3232235777
3232236033
I believe that the difference is how PowerShell is handling newlines. Line 16 ($x += $s[$_]) appears to be reading "1`n192" in the same token, rather than receiving each element as a separate $ip from the foreach statement.
The Get-Member command shows that $example | Get-Member is an instance of System.String just like Get-Content .\example.txt | Get-Member.
I would like for my program to correctly accept input from text files and also from strings. What am I missing here? Why is Get-Content being parsed differently from a multi-line string?

Put the IP Addresses in an object array:
$example = #("192.168.1.1", "192.168.2.1")
$example | convert-IPtointeger
result:
3232235777
3232236033
The reason it is different is because you are using Foreach-Object against a string:
$example1 = #("192.168.1.1", "192.168.2.1")
$example1.GetType()
$example2 = "192.168.1.1
192.168.2.1"
$example2.GetType()
$example3 = (Get-Content "C:\Users\owain.esau\Desktop\links.txt" )
$example3.GetType()
This returns the following
IsPublic IsSerial Name BaseType
-------- -------- ---- --------
True True Object[] System.Array
True True String System.Object
True True Object[] System.Array

Related

Generated string names in Calculated Properties aren't accepted by select cmdlet

I want to generate the following table:
AAA BBB CCC
--- --- ---
10 10 10
10 10 10
10 10 10
10 10 10
10 10 10
So I write the following code using a foreach loop to generate the column names:
$property = #('AAA', 'BBB', 'CCC') | foreach {
#{ name = $_; expression = { 10 } }
}
#(1..5) | select -Property $property
But I get the following error saying the name is not a string:
select : The "name" key has a type, System.Management.Automation.PSObject, that is not valid; expected type is System.String.
At line:4 char:11
+ #(1..5) | select -Property $property
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidArgument: (:) [Select-Object], NotSupportedException
+ FullyQualifiedErrorId : DictionaryKeyIllegalValue2,Microsoft.PowerShell.Commands.SelectObjectCommand
To get the code work, I have to convert the $_ to string like below:
$property = #('AAA', 'BBB', 'CCC') | foreach {
#{ name = [string]$_; expression = { 10 } }
}
#(1..5) | select -Property $property
Or like below:
$property = #('AAA', 'BBB', 'CCC') | foreach {
#{ name = $_; expression = { 10 } }
}
$property | foreach { $_.name = [string]$_.name }
#(1..5) | select -Property $property
The question is: the $_ is already a string. Why do I have to convert it to string again? And why select thinks that the name is PSObject?
To confirm that it's already a string, I write the following code to print the type of name:
$property = #('AAA', 'BBB', 'CCC') | foreach {
#{ name = $_; expression = { 10 } }
}
$property | foreach { $_.name.GetType() }
The following result confirms that it's already a string:
IsPublic IsSerial Name BaseType
-------- -------- ---- --------
True True String System.Object
True True String System.Object
True True String System.Object
I know that there are many other easier ways to generate the table. But I want to understand why I have to convert a string to string to make the code work, and why select doesn't think that the string is a string. For what it's worth, my $PSVersionTable.PSVersion is:
Major Minor Build Revision
----- ----- ----- --------
5 1 18362 1474
You're seeing the unfortunate effects of incidental, normally invisible [psobject] wrappers PowerShell uses behind the scenes.
In your case, because the input strings are supplied via the pipeline, they get wrapped in and stored as [psobject] instances in your hashtables, which is the cause of the problem.
The workaround - which is neither obvious nor should it be necessary - is to discard the wrapper by accessing .psobject.BaseObject:
$property = 'AAA', 'BBB', 'CCC' | ForEach-Object {
#{ name = $_.psobject.BaseObject; expression = { 10 } }
}
1..5 | select -Property $property
Note:
In your case, a simpler alternative to .psobject.BaseObject (see the conceptual about_Intrinsic Members help topic) would have been to call .ToString(), given that you want a string.
To test a given value / variable for the presence of such a wrapper, use -is [psobject]; with your original code, the following yields $true, for instance:
$property[0].name -is [psobject]
Note, however, that this test is meaningless for [pscustomobject] instances, where it is always $true (custom objects are in essence [psobject] instances without a .NET base objects - they only have dynamic properties).
That the normally invisible [psobject] wrappers situationally, obscurely result in behavioral differences is arguably a bug and the subject of GitHub issue #5579.
Simpler and faster alternative, using the .ForEach() array method:
$property = ('AAA', 'BBB', 'CCC').ForEach({
#{ name = $_; expression = { 10 } }
})
1..5 | select -Property $property
Unlike the pipeline, the .ForEach() method does not wrap $_ in [psobject], so the problem doesn't arise and no workaround is needed.
Using the method is also faster, although note that, unlike the pipeline, it must collect all its input in memory up front (clearly not a problem with an array literal).

Make the contents of a .md file the body of an email for outlook using powershell

I want to take the contents of an .md file and have it appear in the body of a generated email for outlook. I can generate the email just find but the body gives the following error and I have yet to find a work around.
Error:
The object does not support this method.
At line:6 char:1
+ $new.HTMLBody = $a
+ ~~~~~~~~~~~~~~
+ CategoryInfo : OperationStopped: (:) [], COMException
+ FullyQualifiedErrorId : System.Runtime.InteropServices.COMException
Code:
$out= New-Object -comObject Outlook.Application
# $sign= Get-Content "C:\Users\Roaming\Microsoft\Signatures\sign.htm"
$recipient= "user#.com"
$new= $out.CreateItem(0)
$new.Subject = "Meeting details"
$a = Get-Content -Path "c:\temp\file.md"
$new.HTMLBody = $a
$new.Recipients.Add($recipient)
$new.save()
# $new.HTMLBody += $sign
$display= $new.GetInspector
$display.Display()
To get around the error message you posted, you need to read your file as a string rather than an array of strings. The reason is because $new.Body expects a string. By default, Get-Content returns an array with each line of a file being an element of that array. You can change this behavior with the -Raw switch, which will read the contents as one string.
$a = Get-Content -Path "c:\temp\file.md" -Raw
If the -Raw switch changes the newline formatting, you can always just join the default Get-Content array with newline characters of your choosing.
$a = Get-Content -Path "c:\temp\file.md"
$new.HTMLBody = $a -join "`r`n"
You can see the type differences in your original code using the GetType() method available to PowerShell objects.
$a.GetType()
IsPublic IsSerial Name BaseType
-------- -------- ---- --------
True True Object[] System.Array
$new.Body.GetType()
IsPublic IsSerial Name BaseType
-------- -------- ---- --------
True True String System.Object

Not able to parse multiline array in powershell

My aws cli returns multiline array with nested hash, I am not able to parse output to get appropriate values from it
>Write-Host $a
[
[
[
{
"VolumeID": "vol-fxxxxxx"
}
]
]
]
If I do $a[0], it returns first line i.e. "[" same with incremental indexs.
How I can parse this array and get the volumeID from it ?
Thanks
Additional details :
Thanks for your valuable answers. Here is the class name of the above object :
> $a.getType()
IsPublic IsSerial Name BaseType
-------- -------- ---- --------
True True Object[] System.Array
> $a | Where-Object ($_ -like "*Volume*")
> $a | ConvertFrom-Json
ConvertFrom-Json : Invalid JSON primitive:
Microsoft.PowerShell.Commands.Internal.Format.FormatEntryData.
At line:1 char:6
+ $a | ConvertFrom-Json
+ ~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (:) [ConvertFrom-Json], ArgumentException
+ FullyQualifiedErrorId : System.ArgumentException,Microsoft.PowerShell.Commands.ConvertFromJsonCommand
Hi Martin Brandl, TessellatingHeckler I have updated with your question, could you please help me out on the same.
Thanks
same with incremental indexs
I can't make sense of incremental indexes doing the same, and returning the first line, but it looks like it's either:
A multiline string ($a.gettype() has Name: String):
$b = $a | ConvertFrom-Json
$b[0][0][0].VolumeID
or, an array of string ($a.gettype() Name is Object[] or String[]), do this first, then convert from JSON:
$a = -join $a
You could use the where-object cmdlet.
$a | where-object {$_ -like "*Volume*"}
More information on the where-object cmdlet can be found here: https://technet.microsoft.com/en-us/library/ee177028.aspx
Thanks, Tim.

Array.Find on powershell array

How can I use the Array.Find method in powershell?
For example:
$a = 1,2,3,4,5
[Array]::Find($a, { args[0] -eq 3 })
gives
Cannot find an overload for "Find" and the argument count: "2".
At line:3 char:1
+ [Array]::Find($a, { $args[0] -eq 3 })
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (:) [], MethodException
+ FullyQualifiedErrorId : MethodCountCouldNotFindBest
The array class has the methods I expect, as given by:
PS> [Array] | Get-Member -Static
TypeName: System.Array
Name MemberType Definition
---- ---------- ----------
Find Method static T Find[T](T[] array, System.Predicate[T] match)
Should the array be casted to something else to match the T[] type? I know there are other ways to achieve the find functionality, but I was wondering why this doesn't work.
There is no need to use Array.Find, a regular where clause would work fine:
$a = #(1,2,3,4,5)
$a | where { $_ -eq 3 }
Or this (as suggested by #mjolinor):
$a -eq 3
Or this (returns $true or $false):
$a -contains 3
Where clause supports any type of objects, not just basic types, like this:
$a | where { $_.SomeProperty -eq 3 }
You need to cast the ScriptBlock as a Predicate[T]. Consider the following example:
[Array]::Find(#(1,2,3), [Predicate[int]]{ $args[0] -eq 1 })
# Result: 1
The reason that you received the error, is because there was no matching method overload, in the case where you're passing in a PowerShell ScriptBlock. As you noted in your Get-Member output, there is no Find() method overload that accepts a ScriptBlock as its second parameter.
[Array]::Find(#(1,2,3), { $args[0] -eq 1 })
Cannot find an overload for "Find" and the argument count: "2".
At line:1 char:17
+ [Array]::Find(#(1,2,3), { $_ -eq 1 })
+ ~~~~~
+ CategoryInfo : NotSpecified: (:) [], MethodException
+ FullyQualifiedErrorId : MethodCountCouldNotFindBest
Another option would be using an ArrayList, which provides a Contains method:
PS C:\> [Collections.ArrayList]$a = 'a', 'b', 'c'
PS C:\> $a.Contains('b')
True
PS C:\> $a.Contains('d')
False
Or, as #Neolisk mentioned in the comments, you could use PowerShell's -contains operator:
PS C:\> $a = 'a', 'b', 'c'
PS C:\> $a -contains 'b'
True
PS C:\> $a -contains 'd'
False
Trevor Sullivan's answer is the right one, not only for Find() static method, but for FindIndex() as well.
When you've got several NIC cards with both ipv4 & ipv6 active on your servers and want to check the ipv4 IP/netmask pairs, something like this is good :
$NetworkAdapters = Get-WmiObject Win32_NetworkAdapterConfiguration -Filter 'IPEnabled = True' | Select-Object -Property Description, IPAddress, IPSubnet, DefaultIPGateway, DNSServerSearchOrder, DNSDomain
$NetworkAdapters | % {
"Adapter {0} :" -f $_.Description
# array'ing to avoid failure against single homed netcards
$idx = [System.Array]::FindIndex(#($_.IPAddress), [Predicate[string]]{ $args[0] -match "\d+.\d+.\d+.\d+" })
" IP {0} has netmask {1}" -f #($_.IPAddress[$idx]), #($_.IPSubnet)[$idx]
}
My point is it works like a charm on 2012 WinPE, and fails on a production Win7 wks. Anyone got an idea ?
This was run across ~6 million items in a system.array using both methods
$s=get-date
$([array]::FindALL($OPTArray,[Predicate[string]]{ $args[0] -match '^004400702(_\d{5})?' })).count
$(New-TimeSpan -Start $s -End $(get-date)).TotalSeconds
20 items
33.2223219 seconds
$s=get-date
$($OPTArray | where { $_ -match '^004400702(_\d{5})?'}).count
$(New-TimeSpan -Start $s -End $(get-date)).TotalSeconds
20 items
102.1832173 seconds

How to test for $null array in PowerShell

I'm using an array variable in PowerShell 2.0. If it does not have a value, it will be $null, which I can test for successfully:
PS C:\> [array]$foo = $null
PS C:\> $foo -eq $null
True
But when I give it a value, the test for $null does not return anything:
PS C:\> [array]$foo = #("bar")
PS C:\> $foo -eq $null
PS C:\>
How can "-eq $null" give no results? It's either $null or it's not.
What is the correct way to determine if an array is populated vs. $null?
It's an array, so you're looking for Count to test for contents.
I'd recommend
$foo.count -gt 0
The "why" of this is related to how PSH handles comparison of collection objects
You can reorder the operands:
$null -eq $foo
Note that -eq in PowerShell is not an equivalence relation.
if($foo -eq $null) { "yes" } else { "no" }
help about_comparison_operators
displays help and includes this text:
All comparison operators except the
containment operators (-contains,
-notcontains) and type operators (-is, -isnot) return a Boolean value when the input to the operator (the value
on the left side of the operator) is a
single value (a scalar). When the
input is a collection of values, the
containment operators and the type
operators return any matching values.
If there are no matches in a
collection, these operators do not
return anything. The containment
operators and type operators always
return a Boolean value.
If your solution requires returning 0 instead of true/false, I've found this to be useful:
PS C:\> [array]$foo = $null
PS C:\> ($foo | Measure-Object).Count
0
This operation is different from the count property of the array, because Measure-Object is counting objects. Since there are none, it will return 0.
The other answers address the main thrust of the question, but just to comment on this part...
PS C:\> [array]$foo = #("bar")
PS C:\> $foo -eq $null
PS C:\>
How can "-eq $null" give no results? It's either $null or it's not.
It's confusing at first, but that is giving you the result of $foo -eq $null, it's just that the result has no displayable representation.
Since $foo holds an array, $foo -eq $null means "return an array containing the elements of $foo that are equal to $null". Are there any elements of $foo that are equal to $null? No, so $foo -eq $null should return an empty array. That's exactly what it does, the problem is that when an empty array is displayed at the console you see...nothing...
PS> #()
PS>
The array is still there, even if you can't see its elements...
PS> #().GetType()
IsPublic IsSerial Name BaseType
-------- -------- ---- --------
True True Object[] System.Array
PS> #().Length
0
We can use similar commands to confirm that $foo -eq $null is returning an array that we're not able to "see"...
PS> $foo -eq $null
PS> ($foo -eq $null).GetType()
IsPublic IsSerial Name BaseType
-------- -------- ---- --------
True True Object[] System.Array
PS> ($foo -eq $null).Length
0
PS> ($foo -eq $null).GetValue(0)
Exception calling "GetValue" with "1" argument(s): "Index was outside the bounds of the array."
At line:1 char:1
+ ($foo -eq $null).GetValue(0)
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (:) [], MethodInvocationException
+ FullyQualifiedErrorId : IndexOutOfRangeException
Note that I am calling the Array.GetValue method instead of using the indexer (i.e. ($foo -eq $null)[0]) because the latter returns $null for invalid indices and there's no way to distinguish them from a valid index that happens to contain $null.
We see similar behavior if we test for $null in/against an array that contains $null elements...
PS> $bar = #($null)
PS> $bar -eq $null
PS> ($bar -eq $null).GetType()
IsPublic IsSerial Name BaseType
-------- -------- ---- --------
True True Object[] System.Array
PS> ($bar -eq $null).Length
1
PS> ($bar -eq $null).GetValue(0)
PS> $null -eq ($bar -eq $null).GetValue(0)
True
PS> ($bar -eq $null).GetValue(0) -eq $null
True
PS> ($bar -eq $null).GetValue(1)
Exception calling "GetValue" with "1" argument(s): "Index was outside the bounds of the array."
At line:1 char:1
+ ($bar -eq $null).GetValue(1)
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (:) [], MethodInvocationException
+ FullyQualifiedErrorId : IndexOutOfRangeException
In this case, $bar -eq $null returns an array containing one element, $null, which has no visual representation at the console...
PS> #($null)
PS> #($null).GetType()
IsPublic IsSerial Name BaseType
-------- -------- ---- --------
True True Object[] System.Array
PS> #($null).Length
1
How do you want things to behave?
If you want arrays with no elements to be treated the same as unassigned arrays, use:
[array]$foo = #() #example where we'd want TRUE to be returned
#($foo).Count -eq 0
If you want a blank array to be seen as having a value (albeit an empty one), use:
[array]$foo = #() #example where we'd want FALSE to be returned
$foo.PSObject -eq $null
If you want an array which is populated with only null values to be treated as null:
[array]$foo = $null,$null
#($foo | ?{$_.PSObject}).Count -eq 0
NB: In the above I use $_.PSObject over $_ to avoid [bool]$false, [int]0, [string]'', etc from being filtered out; since here we're focussed solely on nulls.
Watch out for switch. It will never run with a null array, for example as the output of an empty directory.
switch ( $null ) { default { 'yes' } }
yes
switch ( #() ) { default { 'yes' } } # no output
mkdir foo
switch ( dir foo ) { default { 'yes' } } # no output
Not all of these answers work for me. I ended up doing this, treating it like a boolean.
$foo = #()
if (! $foo) { 'empty array' }
empty array
Actually, I came across an arraylist inside an object.
[pscustomobject]#{config = [Collections.ArrayList]#()} | ? { ! $_.config }
config
------
{}