Select all unique third octet in a list of IP addresses with PowerShell - powershell

I have a list of IP addresses. They all start with 10.10. I want all the unique values of the third octet. This way I can count how many of that unique value there are.
10.10.26.251
10.10.27.221
10.10.26.55
10.10.31.12
10.10.12.31
10.10.31.11
10.10.27.15
10.10.26.5
When I am done I want to know that I have 3 .26 network devices, 2 27, and so on so forth. Other than breaking down the octet with a split and looping through each one, I can't think of any single liners. Any suggestions?

here's a small variant. [grin] i already had this before noticing the other answers - and it is a tad different.
what it does ...
creates a collection of IPv4 address objects to work with
groups them by a calculated property [the 3rd octet]
creates a [PSCustomObject] for each resulting group
sends it to the $Octet3_Report variable
shows it on screen
output to a CSV file would be easy at that point. here's the code ...
$IP_List = #(
[ipaddress]'10.10.26.251'
[ipaddress]'10.10.27.221'
[ipaddress]'10.10.26.55'
[ipaddress]'10.10.31.12'
[ipaddress]'10.10.12.31'
[ipaddress]'10.10.31.11'
[ipaddress]'10.10.27.15'
[ipaddress]'10.10.26.5'
)
$Octet3_Report = $IP_List |
Group-Object -Property {$_.ToString().Split('.')[2]} |
ForEach-Object {
[PSCustomObject]#{
Octet_3 = $_.Name
Count = $_.Count
}
}
$Octet3_Report
on screen output ...
Octet_3 Count
------- -----
26 3
27 2
31 2
12 1

It's like me to figure it out after the fact.
The Return contains the dns records. The IP address are stored inside recorddata. I pull the end of the IP address off. Then loop through grabbing only the range and count with a foreach loop to make it cleaner.
$DNSRecordCounts = #()
$Ranges = ($Return | where-object {$_.recorddata -like "10.10.*"}).recorddata -replace "\.\d{1,3}$" | select -Unique
foreach ($range in $Ranges) {
$DNSRecordCounts += [pscustomobject][ordered]#{
IPRange = $range
Count = ($Return | Where-Object {$_.recorddata -like "$($range).*"}).Count
}
}

Based on your question and what I can infer from your own answer, if you are looking for something a little more like "idiomatic" PowerShell you want the following:
$Return `
| Select-Object -ExpandProperty recorddata `
| ForEach-Object {
$_ -match "\d+\.\d+\.(?<octet>\d+)\.\d+" | Out-Null
$Matches.octet
} `
| Group-Object `
| ForEach-Object {
[PSCustomObject]#{
Octet = $_.Name
Count = $_.Count
}
}

Related

matching data across two arrays and combining with additional data in array

The Goal
See if $SP.ip is in $NLIP.IpRanges and if it is, add $NLIP.IpRanges and $NLIP.DisplayName to the $SP array or all into a new array.
The Arrays
Array 1 is $SP, it's a CSV import and has the properties 'name' and 'ip', it looks like this:
name: bob
ip: 1.9.8.2
Array 2 is $NLIP and has the relevant properties 'IpRanges' and 'DisplayName'. It's fetched from: $NLIP = Get-AzureADMSNamedLocationPolicy | where-object {$_.OdataType -eq "#microsoft.graph.ipNamedLocation"}, it looks like this:
DisplayName : Named Location 1
IpRanges : {class IpRange {
CidrAddress: 16.29.28.9/28 #fictitious CIDR
}
, class IpRange {
CidrAddress: 1.9.8.3/28 #fictitious CIDR
}
}
The Code / the problem
I'm using IPInRange.ps1 function from https://github.com/omniomi/PSMailTools to find if the IP is in the range. It works like so:
> IPInRange 1.9.8.2 1.9.8.3/28
True
I also worked out that $NLTP.IpRanges.split() | Where-Object ($_ -like "*/*"} can return all the ranges, but $NLIP | Where-Object {$_.IpRanges.split() -like "*/*"} doesn't. I would naturally use the second to keep the variable in the pipe to return the DisplayName. So I'm struggling on how to pull the individual ranges out in such a way that I can then add the 'IpRange' and 'DisplayName' to an array.
Also, maybe it's because I haven't worked out the above issue, but I'm struggling to think how I would iterate through both arrays and combine them into one. I know I would probably enter into a foreach ($item in $SP) and create a temporary array, but after that it's getting hazy.
The result
What I'm hoping to have in the end is:
name: bob
ip: 1.9.8.2
IpRange: 1.9.8.3/28 #fictitious CIDR
DisplayName: Named Location 1
thanks in advance.
I believe this will work for you if I understood the NLIP construct correctly.
We will loop through all the SP objects and see if we can find any NLIP that match the IP range using the IPinRange function you linked. We will then add the 2 properties you want to the SP object if matched and finally pass thru to the pipeline or you can append | export-csv -path YourPath to the end if you would like to send to a csv file
$SP | ForEach-Object {
$target = $_
$matched = $NLIP | ForEach-Object {
$item = $_
# Using where to single out matching range using IPinRange function
$_.IpRanges.Where({ IPInRange -IPAddress $target.ip -Range $_.CidrAddress }) |
ForEach-Object {
# for matching range output custom object containing the displayname and iprange
[PSCustomObject]#{
DisplayName = $item.DisplayName
IpRange = $_.CidrAddress
}
}
}
# add the 2 properties (DisplayName and IpRange) from the match to the original $SP
# object and then pass thru
$target | Add-Member -NotePropertyName DisplayName -NotePropertyValue $matched.DisplayName
$target | Add-Member -NotePropertyName IpRange -NotePropertyValue $matched.IpRange -PassThru
}
By the way, this is how I envisioned the NLIP objects and what I tested with
$NLIP = #(
[pscustomobject]#{
DisplayName = 'Named location 1'
IpRanges = #(
[pscustomobject]#{
CidrAddress = '16.29.28.9/28'
},
[pscustomobject]#{
CidrAddress = '1.9.8.3/28'
}
)
},
[pscustomobject]#{
DisplayName = 'Named location 2'
IpRanges = #(
[pscustomobject]#{
CidrAddress = '16.29.28.25/28'
},
[pscustomobject]#{
CidrAddress = '1.9.8.25/28'
}
)
}
)
Let's to shed some lights in the hazy darkness by first creating a Minimal, Reproducible Example (mcve):
$SP = ConvertFrom-Csv #'
IP, Name
1.9.8.2, BOB
10.10.10.10, Apple
16.29.28.27, Pear
16.30.29.28, Banana
'#
$NLIP = ConvertFrom-Csv #'
IPRange, SubNet
16.29.28.9/28, NetA
1.9.8.3/28, NetB
'#
To tackle this, you need two loops where the second loop is inside the first loop. For the outer loop you might use the ForEach-Object cmdlet which lets you stream each object and with that actually use less memory (assuming that you import the data from a file and eventually export it to a new file). Within the inner loop you might than cross link each IP address with the IPRange using the function you refer to and in case the condition is true create a new PSCustomObject:
$SP |ForEach-Object { # | Import-Csv .\SP.csv |ForEach-Object { ...
ForEach($SubNet in $NLIP) {
if (IPInRange $_.IP $SubNet.IPRange) {
[PSCustomObject]#{
IP = $_.IP
Name = $_.Name
IPRange = $SubNet.IPRange
SubNet = $SubNet.SubNet
}
}
}
} # | Export-Csv .\Output.csv
Which results in:
IP Name IPRange SubNet
-- ---- ------- ------
1.9.8.2 BOB 1.9.8.3/28 NetB
16.29.28.27 Pear 16.29.28.9/8 NetA
16.30.29.28 Banana 16.29.28.9/8 NetA
But as you are considering 3rd party scripts anyways, you might as well use this Join-Object script/Join-Object Module (see also: In Powershell, what's the best way to join two tables into one?):
$SP |Join $NLIP -Using { IPInRange $Left.IP $Right.IPRange }
Which gives the same results.

powershell winform searchbox shows results incorrect [duplicate]

I'm using Powershell to set up IIS bindings on a web server, and having a problem with the following code:
$serverIps = gwmi Win32_NetworkAdapterConfiguration
| Where { $_.IPAddress }
| Select -Expand IPAddress
| Where { $_ -like '*.*.*.*' }
| Sort
if ($serverIps.length -le 1) {
Write-Host "You need at least 2 IP addresses for this to work!"
exit
}
$primaryIp = $serverIps[0]
$secondaryIp = $serverIps[1]
If there's 2+ IPs on the server, fine - Powershell returns an array, and I can query the array length and extract the first and second addresses just fine.
Problem is - if there's only one IP, Powershell doesn't return a one-element array, it returns the IP address (as a string, like "192.168.0.100") - the string has a .length property, it's greater than 1, so the test passes, and I end up with the first two characters in the string, instead of the first two IP addresses in the collection.
How can I either force Powershell to return a one-element collection, or alternatively determine whether the returned "thing" is an object rather than a collection?
Define the variable as an array in one of two ways...
Wrap your piped commands in parentheses with an # at the beginning:
$serverIps = #(gwmi Win32_NetworkAdapterConfiguration
| Where { $_.IPAddress }
| Select -Expand IPAddress
| Where { $_ -like '*.*.*.*' }
| Sort)
Specify the data type of the variable as an array:
[array]$serverIps = gwmi Win32_NetworkAdapterConfiguration
| Where { $_.IPAddress }
| Select -Expand IPAddress
| Where { $_ -like '*.*.*.*' }
| Sort
Or, check the data type of the variable...
IF ($ServerIps -isnot [array])
{ <error message> }
ELSE
{ <proceed> }
Force the result to an Array so you could have a Count property. Single objects (scalar) do not have a Count property. Strings have a length property so you might get false results, use the Count property:
if (#($serverIps).Count -le 1)...
By the way, instead of using a wildcard that can also match strings, use the -as operator:
[array]$serverIps = gwmi Win32_NetworkAdapterConfiguration -filter "IPEnabled=TRUE" | Select-Object -ExpandProperty IPAddress | Where-Object {($_ -as [ipaddress]).AddressFamily -eq 'InterNetwork'}
You can either add a comma(,) before return list like return ,$list or cast it [Array] or [YourType[]] at where you tend to use the list.
If you declare the variable as an array ahead of time, you can add elements to it - even if it is just one...
This should work...
$serverIps = #()
gwmi Win32_NetworkAdapterConfiguration
| Where { $_.IPAddress }
| Select -Expand IPAddress
| Where { $_ -like '*.*.*.*' }
| Sort | ForEach-Object{$serverIps += $_}
You can use Measure-Object to get the actual object count, without resorting to an object's Count property.
$serverIps = gwmi Win32_NetworkAdapterConfiguration
| Where { $_.IPAddress }
| Select -Expand IPAddress
| Where { $_ -like '*.*.*.*' }
| Sort
if (($serverIps | Measure).Count -le 1) {
Write-Host "You need at least 2 IP addresses for this to work!"
exit
}
Return as a referenced object, so it never converted while passing.
return #{ Value = #("single data") }
I had this problem passing an array to an Azure deployment template. If there was one object, PowerShell "converted" it to a string. In the example below, $a is returned from a function that gets VM objected according to the value of a tag. I pass the $a to the New-AzureRmResourceGroupDeployment cmdlet by wrapping it in #(). Like so:
$TemplateParameterObject=#{
VMObject=#($a)
}
New-AzureRmResourceGroupDeployment -ResourceGroupName $RG -Name "TestVmByRole" -Mode Incremental -DeploymentDebugLogLevel All -TemplateFile $templatePath -TemplateParameterObject $TemplateParameterObject -verbose
VMObject is one of the template's parameters.
Might not be the most technical / robust way to do it, but it's enough for Azure.
Update
Well the above did work. I've tried all the above and some, but the only way I have managed to pass $vmObject as an array, compatible with the deployment template, with one element is as follows (I expect MS have been playing again (this was a report and fixed bug in 2015)):
[void][System.Reflection.Assembly]::LoadWithPartialName("System.Web.Extensions")
foreach($vmObject in $vmObjects)
{
#$vmTemplateObject = $vmObject
$asJson = (ConvertTo-Json -InputObject $vmObject -Depth 10 -Verbose) #-replace '\s',''
$DeserializedJson = (New-Object -TypeName System.Web.Script.Serialization.JavaScriptSerializer -Property #{MaxJsonLength=67108864}).DeserializeObject($asJson)
}
$vmObjects is the output of Get-AzureRmVM.
I pass $DeserializedJson to the deployment template' parameter (of type array).
For reference, the lovely error New-AzureRmResourceGroupDeployment throws is
"The template output '{output_name}' is not valid: The language expression property 'Microsoft.WindowsAzure.ResourceStack.Frontdoor.Expression.Expressions.JTokenExpression'
can't be evaluated.."
There is a way to deal with your situation. Leave most of you code as-is, just change the way to deal with the $serverIps object. This code can deal with $null, only one item, and many items.
$serverIps = gwmi Win32_NetworkAdapterConfiguration
| Where { $_.IPAddress }
| Select -Expand IPAddress
| Where { $_ -like '*.*.*.*' }
| Sort
# Always use ".Count" instead of ".Length".
# This works on $null, only one item, or many items.
if ($serverIps.Count -le 1) {
Write-Host "You need at least 2 IP addresses for this to work!"
exit
}
# Always use foreach on a array-possible object, so that
# you don't have deal with this issue anymore.
$serverIps | foreach {
# The $serverIps could be $null. Even $null can loop once.
# So we need to skip the $null condition.
if ($_ -ne $null) {
# Get the index of the array.
# The #($serverIps) make sure it must be an array.
$idx = #($serverIps).IndexOf($item)
if ($idx -eq 0) { $primaryIp = $_ }
if ($idx -eq 1) { $secondaryIp = $_ }
}
}
In PowerShell Core, there is a .Count property exists on every objects. In Windows PowerShell, there are "almost" every object has an .Count property.

Split array column and result into extra Colums

I have a table (CSV) which shows all the users that have ever logged on to a bunch of computers. Users can have 2 accounts, one username starts with "a" the other with "b", like a100 and b100 (the user behind is the same person).
Now I need to get the computers that have more then 2 accounts logged on which do not belong the same users. So A64 and B64 are not reported as separate users.
Here is the base list I have:
PC1,A64,B52,B64,A41
PC2,A51,B42,B51,A23
PC3,A42,B51
PC4,A5,B5
PC5,A1,B1,A14,A6
My plan was to split the "User"-column into more columns, so the table would look like this:
Computername,user1,user2,user3,user4,UserX
After this was done, I could iterate through the table and remove the leading letter in the Username, then I would try to get rid of doubles.
Do you think that makes sense?
Now I got stuck in the first task already. I know how to iterate though the second Column but how do I managed to get the result into another array so the output would be like:
Computername,user1,user2,user3,user4,UserX
Can you help me split?
$UserComputers = import-csv -Delimiter ";" "input.csv" -Header
'Computername','user1','user2','user3','user4'
$UserComputers | Select-Object *,
#{n='User1';e={$_.User1.Split(',')[0]}},
#{n='User2';e={$_.User1.Split(',')[1]}}
I get the error: Select-Object : The property cannot be processed because the property "User1" already exists.
It is useful to make "user" an array.
Get-Content "input.csv" | foreach {
$name, $users = $_.Split(",")
[pscustomobject]#{ Name = $name; Users = $users }
} | Where-Object { ($_.Users.Substring(1) | Select-Object -Unique).Count -gt 2 }
The output is below.
Name Users
---- -----
PC1 {A64, B52, B64, A41}
PC2 {A51, B42, B51, A23}
PC3 {A42, B51}
PC5 {A1, B1, A14, A6}
Input File (input.csv)
PC1,A64,B52,B64,A41
PC2,A51,B42,B51,A23
PC3,A42,B51
PC4,A5,B5
PC5,A1,B1,A14,A6
Powershell Script
Get-Content -Path .\input.csv |
Select-Object #{ Name = "Computer"; Expression = { $_.Split(',')[0] } },
#{ Name="Users"; Expression = { $_.Split(',')[1..($_.Split(',').Length-1)] |
Foreach-Object { $_.Substring(1) } | Select-Object -Unique } } |
Where-Object { $_.Users.Count -gt 2 }
Result:
Computer Users
------------- -----
PC1 {64, 52, 41}
PC2 {51, 42, 23}
PC5 {1, 14, 6}
P.S. Bonus: If you want to see more than 4 elements of the array on the screen change the variable
$FormatEnumerationLimit = 20
Explanation of the variable meaning
If your file is like your base list, you can do the following to build a new file with all the columns you need:
$maxColCount = 0
$data = get-content input.csv
foreach ($line in $data) {
$MaxColCount = [math]::Max($maxcolcount,($line -split ",").count)
}
$headers = #("ComputerName")
$MaxUserCount = $MaxColCount - 1
Foreach ($c in (1..$MaxUserCount)) {
$Headers += "User$c"
}
$Headers = $Headers -join ","
$Headers,$data | Set-Content "output.csv"
The code above assumes input.csv has the following format and each column after the first is a user:
PC1,A64,B52,B64,A41
PC2,A51,B42,B51,A23
PC3,A42,B51
PC4,A5,B5
PC5,A1,B1,A14,A6

Parsing multiple valus in multiple variables

I am trying to find a way to execute a command in powershell and put each line of the result in a different variable to use them later in my script. For example if i execute the below command:
C:\> Get-VMHost -Name hq-esxi-prod-01a.nsx.gss | Get-VM | select Name
I will get the below:
Name
----
HQ-LinServ-01a
HQ-Win2012-01a
HQ-Web-02a
I want to have a script that will add each line in a different variable in a script (excluding the first which is name).
how can i do that.
Thank you for your help.
You can use Set-Variable in a loop to put each value in a separate variable:
$i = 0
... | Get-Vm | Select-Objet -Expand Name | ForEach-Object {
Set-Variable -Name "vm$i" -Value $_
$i++
}
However, that usually isn't good advice. It's more common to put all names in one (array) variable:
$vmList = ...| Get-Vm | Select-Object -Expand Name
so you can access individual names via $vmList[<index>], or (if you need access by some kind of name) in a hashtable:
$i = 0
$vmList = #{}
... | Get-Vm | Select-Objet -Expand Name | ForEach-Object {
$vmList["vm$i"] = $_
$i++
}
Best practice would depend on the particular scenario you need this for, though.
Thank you for your reply,
I have tried you answer but it seems that i am using PowerCLI for VMware it does not include Select-Object -Expand (not sure i had an exception), However your answer have mad me reach to a suitable answer for this.
I have used the below and it worked fine using foreach and adding the values in an array and then reading them as below:
$p1vmname = Get-VMHost -Name hq-esxi-prod-01a.nsx.gss | Get-VM | select Name
$p1vmlist = #()
foreach ($p1line in $p1vmname)
{
$p1vmlist += $p1line
}
$p1 = 0
do {
$x = $p1+1
Write-Host -BackgroundColor:Black -ForegroundColor:Yellow "vm number $x is "$p1vmlist[$p1]"."
$p1++
}
until ($p1 -eq $p1vmc)
}
However when using this the names was not totally correct as they had some additional characters as below:
vm number 1 is #{Name=HQ-Web-01a}
vm number 2 is #{Name=HQ-LinServ-01a}
vm number 3 is #{Name=HQ-Win2012-01a}
so i used split and trim to get rid of these as below and worked fine.
$p1vmname = Get-VMHost -Name hq-esxi-prod-01a.nsx.gss | Get-VM | select Name
$p1vmlist = #()
foreach ($p1line in $p1vmname)
{
$p1vmlist += $p1line
}
$p1 = 0
do {
$x = $p1+1
$p1vmlist[$p1] = ($p1vmlist[$p1]) -split("=") | Select-Object -Last 1
$p1vmlist[$p1] = $p1vmlist[$p1].trimend("}")
Write-Host -BackgroundColor:Black -ForegroundColor:Yellow "vm number $x is "$p1vmlist[$p1]"."
$p1++
}
until ($p1 -eq $p1vmc)
}
Output:
vm number 1 is HQ-Web-01a .
vm number 2 is HQ-LinServ-01a .
vm number 3 is HQ-Win2012-01a .
Thank you so much for your answer that helped me a lot.
I am really enjoying scripting now.

How can I force Powershell to return an array when a call only returns one object?

I'm using Powershell to set up IIS bindings on a web server, and having a problem with the following code:
$serverIps = gwmi Win32_NetworkAdapterConfiguration
| Where { $_.IPAddress }
| Select -Expand IPAddress
| Where { $_ -like '*.*.*.*' }
| Sort
if ($serverIps.length -le 1) {
Write-Host "You need at least 2 IP addresses for this to work!"
exit
}
$primaryIp = $serverIps[0]
$secondaryIp = $serverIps[1]
If there's 2+ IPs on the server, fine - Powershell returns an array, and I can query the array length and extract the first and second addresses just fine.
Problem is - if there's only one IP, Powershell doesn't return a one-element array, it returns the IP address (as a string, like "192.168.0.100") - the string has a .length property, it's greater than 1, so the test passes, and I end up with the first two characters in the string, instead of the first two IP addresses in the collection.
How can I either force Powershell to return a one-element collection, or alternatively determine whether the returned "thing" is an object rather than a collection?
Define the variable as an array in one of two ways...
Wrap your piped commands in parentheses with an # at the beginning:
$serverIps = #(gwmi Win32_NetworkAdapterConfiguration
| Where { $_.IPAddress }
| Select -Expand IPAddress
| Where { $_ -like '*.*.*.*' }
| Sort)
Specify the data type of the variable as an array:
[array]$serverIps = gwmi Win32_NetworkAdapterConfiguration
| Where { $_.IPAddress }
| Select -Expand IPAddress
| Where { $_ -like '*.*.*.*' }
| Sort
Or, check the data type of the variable...
IF ($ServerIps -isnot [array])
{ <error message> }
ELSE
{ <proceed> }
Force the result to an Array so you could have a Count property. Single objects (scalar) do not have a Count property. Strings have a length property so you might get false results, use the Count property:
if (#($serverIps).Count -le 1)...
By the way, instead of using a wildcard that can also match strings, use the -as operator:
[array]$serverIps = gwmi Win32_NetworkAdapterConfiguration -filter "IPEnabled=TRUE" | Select-Object -ExpandProperty IPAddress | Where-Object {($_ -as [ipaddress]).AddressFamily -eq 'InterNetwork'}
You can either add a comma(,) before return list like return ,$list or cast it [Array] or [YourType[]] at where you tend to use the list.
If you declare the variable as an array ahead of time, you can add elements to it - even if it is just one...
This should work...
$serverIps = #()
gwmi Win32_NetworkAdapterConfiguration
| Where { $_.IPAddress }
| Select -Expand IPAddress
| Where { $_ -like '*.*.*.*' }
| Sort | ForEach-Object{$serverIps += $_}
You can use Measure-Object to get the actual object count, without resorting to an object's Count property.
$serverIps = gwmi Win32_NetworkAdapterConfiguration
| Where { $_.IPAddress }
| Select -Expand IPAddress
| Where { $_ -like '*.*.*.*' }
| Sort
if (($serverIps | Measure).Count -le 1) {
Write-Host "You need at least 2 IP addresses for this to work!"
exit
}
Return as a referenced object, so it never converted while passing.
return #{ Value = #("single data") }
I had this problem passing an array to an Azure deployment template. If there was one object, PowerShell "converted" it to a string. In the example below, $a is returned from a function that gets VM objected according to the value of a tag. I pass the $a to the New-AzureRmResourceGroupDeployment cmdlet by wrapping it in #(). Like so:
$TemplateParameterObject=#{
VMObject=#($a)
}
New-AzureRmResourceGroupDeployment -ResourceGroupName $RG -Name "TestVmByRole" -Mode Incremental -DeploymentDebugLogLevel All -TemplateFile $templatePath -TemplateParameterObject $TemplateParameterObject -verbose
VMObject is one of the template's parameters.
Might not be the most technical / robust way to do it, but it's enough for Azure.
Update
Well the above did work. I've tried all the above and some, but the only way I have managed to pass $vmObject as an array, compatible with the deployment template, with one element is as follows (I expect MS have been playing again (this was a report and fixed bug in 2015)):
[void][System.Reflection.Assembly]::LoadWithPartialName("System.Web.Extensions")
foreach($vmObject in $vmObjects)
{
#$vmTemplateObject = $vmObject
$asJson = (ConvertTo-Json -InputObject $vmObject -Depth 10 -Verbose) #-replace '\s',''
$DeserializedJson = (New-Object -TypeName System.Web.Script.Serialization.JavaScriptSerializer -Property #{MaxJsonLength=67108864}).DeserializeObject($asJson)
}
$vmObjects is the output of Get-AzureRmVM.
I pass $DeserializedJson to the deployment template' parameter (of type array).
For reference, the lovely error New-AzureRmResourceGroupDeployment throws is
"The template output '{output_name}' is not valid: The language expression property 'Microsoft.WindowsAzure.ResourceStack.Frontdoor.Expression.Expressions.JTokenExpression'
can't be evaluated.."
There is a way to deal with your situation. Leave most of you code as-is, just change the way to deal with the $serverIps object. This code can deal with $null, only one item, and many items.
$serverIps = gwmi Win32_NetworkAdapterConfiguration
| Where { $_.IPAddress }
| Select -Expand IPAddress
| Where { $_ -like '*.*.*.*' }
| Sort
# Always use ".Count" instead of ".Length".
# This works on $null, only one item, or many items.
if ($serverIps.Count -le 1) {
Write-Host "You need at least 2 IP addresses for this to work!"
exit
}
# Always use foreach on a array-possible object, so that
# you don't have deal with this issue anymore.
$serverIps | foreach {
# The $serverIps could be $null. Even $null can loop once.
# So we need to skip the $null condition.
if ($_ -ne $null) {
# Get the index of the array.
# The #($serverIps) make sure it must be an array.
$idx = #($serverIps).IndexOf($item)
if ($idx -eq 0) { $primaryIp = $_ }
if ($idx -eq 1) { $secondaryIp = $_ }
}
}
In PowerShell Core, there is a .Count property exists on every objects. In Windows PowerShell, there are "almost" every object has an .Count property.