PowerShell Running Select-String on Variable vs on File

will picture will · Mar 13, 2015 · Viewed 17.2k times · Source

I'm trying to use a PowerShell script to extract my local network IP address from the output of ipconfig. I have gotten it working using a temporary output file but would like to eliminate that step and just use a variable inside the script. I cannot figure out why my script that isn't using a temporary output file won't work. Any help would be much appreciated.

Here are the two scripts. First, the working version:

$input_path = 'C:\Users\Will\Code\Scripts\tmp.txt'
$regex = '\b\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\b'

ipconfig | select-string "IPv4" | select-string -notmatch "192.168.56." > tmp.txt

$ipAddress = select-string -Path $input_path -Pattern $regex -AllMatches | % { $_.Matches } | % { $_.Value }

# Pop up an alert window with the IP address
$wshell = New-Object -ComObject Wscript.Shell
$wshell.Popup($ipAddress,0,"Done",0x1)

# Copy the IP address to the clipboard
$ipAddress | CLIP

And now the almost identical, but non-working version using only variables:

$regex = '\b\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\b'

$input = ipconfig | select-string "IPv4" | select-string -notmatch "192.168.56."

#$input > inputVar.txt

$ipAddress = select-string -inputObject $input -Pattern $regex -AllMatches | % { $_.Matches } | % { $_.Value }

# Pop up an alert window with the IP address
$wshell = New-Object -ComObject Wscript.Shell
$wshell.Popup($ipAddress,0,"Done",0x1)

# Copy the IP address to the clipboard
$ipAddress | CLIP

I am using select-string to find the lines containing 'IPv4' piped into another call to select-string to eliminate the line containing the IP address of my virtual machine. If I enable the test output on line 6 of the non-working script and compare it to the 'tmp.txt' file from the working script, I can see that up to this point, both scripts work identically.

Furthermore, lines 10-15 in both scripts are identical. So I believe that the only line in question is #8.

I have tried it every way I can think of, but always get the same result ("IPv4" rather than the IP address) The first script works as expected and gives me the IP.

Both of these options seem like they should work (and are probably the most logical of all that I've tried), but neither gives me the IP:

$ipAddress = select-string -inputObject $input -Pattern $regex -AllMatches | % { $_.Matches } | % { $_.Value }

and

$ipAddress = $input | select-string -Pattern $regex -AllMatches | % { $_.Matches } | % { $_.Value }

I've even tried eliminating all the extra steps and just making one long pipe like this:

$ipAddress = ipconfig | select-string "IPv4" | select-string -notmatch "192.168.56." | select-string -Pattern $regex -AllMatches | % { $_.Matches } | % { $_.Value }

and just copying to the clipboard like this:

ipconfig | select-string "IPv4" | select-string -notmatch "192.168.56." | select-string -Pattern $regex -AllMatches | % { $_.Matches } | % { $_.Value } | CLIP

But nothing seems to work.

Where have I gone wrong with this?

Thanks!


Updates 03/13/2015 6:30pm

I'm running Windows 7 64bit

Here's the output of $PSVersionTable:

Name Value
---- -----
CLRVersion 2.0.50727.5485
BuildVersion 6.1.7601.17514
PSVersion 2.0
WSManStackVersion 2.0
PSCompatibleVersions {1.0, 2.0}
SerializationVersion 1.1.0.1
PSRemotingProtocolVersion 2.1

And yes, my end goal is just to get the IP. Really, I just wanted to find a quick way to do that. I guess I didn't technically need the IP by itself for what I'm using it for, but then once I got into it, I got pretty caught up in trying to figure the rest of this out just out of curiosity.

Thanks for all the help!

I'm just getting back into coding after a ~10 year break and I'm super rusty.

I'm not sure why my non working version isn't working for me if it's working for you. When I run it, I get "IPv4" in the pop up and on the clipboard, rather than the IP (which I get in the working version that uses the temp file.

I'm going to integrate these regex changes to my current script and play around with your gwmi solution, Matt. An initial run isn't returning anything for me, but I'll toy around with it and let you know.

Let me know if you have any more ideas on why the version with the variable isn't working correctly on my end in light of this updated info. It's totally baffling to me.

Thanks again!


Updates 03/13/2015 10:27pm

Here's the output of $PSVersionTable after the update:

Name Value
---- -----
PSVersion 4.0
WSManStackVersion 3.0
SerializationVersion 1.1.0.1
CLRVersion 4.0.30319.18444
BuildVersion 6.3.9600.16406
PSCompatibleVersions {1.0, 2.0, 3.0, 4.0}
PSRemotingProtocolVersion 2.2


Here is what happens if I run the Piped Commands piece by piece in PowerShell. As you can see, it seems to break on line 14 at: $ipAddress = $ipAddress | % { $_.Matches }

Anyhow, thought this might help:

PS C:\Users\Will\Code> $regex = '\b\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\b'
PS C:\Users\Will\Code> $inputVar = ipconfig | select-string "IPv4" | select-string -notmatch "192\.168\.56\."
PS C:\Users\Will\Code> $inputVar

   IPv4 Address. . . . . . . . . . . : 192.168.0.105


PS C:\Users\Will\Code> $ipAddress = select-string -inputObject $inputVar -Pattern $regex -AllMatches
PS C:\Users\Will\Code> $ipAddress

   IPv4 Address. . . . . . . . . . . : 192.168.0.105


PS C:\Users\Will\Code> $ipAddress = $ipAddress | % { $_.Matches }
PS C:\Users\Will\Code> $ipAddress


Groups   : {IPv4}
Success  : True
Captures : {IPv4}
Index    : 3
Length   : 4
Value    : IPv4



PS C:\Users\Will\Code> $ipAddress = $ipAddress | % { $_.Value }
PS C:\Users\Will\Code> $ipAddress
IPv4
PS C:\Users\Will\Code>

Answer

Matt picture Matt · Mar 13, 2015

Chaining Select-String

I think that is the issue you are having. That and you are treating Select-String like Where-Object. Consider the following

ipconfig |  select-string "IPv4" | % { $_.Matches } | % { $_.Value }

The results of this would be at least one string that is IPv4 since that is exactly what you are asking for. Understand that select-string does not return strings but Microsoft.PowerShell.Commands.MatchInfo objects. I can imagine what is going on through your head would be "but my output should the strings?". That is why you see most uses of Select-String use % { $_.Matches } | % { $_.Value } to extract the matched strings.

There is a difference in what PowerShell is willing to show you on console and the actual underlying object. Not sure if you need more explanation. This edit would make your code work in place:

ipconfig |  select-string "IPv4" | out-string | 
    select-string -notmatch "192.168.56." | out-string |
    select-string -Pattern $regex -AllMatches | % { $_.Matches } | % { $_.Value }

Still, that seems a little odd as the first Select-Strings are acting like Where clauses like I already mentioned. You could also do this then:

ipconfig | Where-Object {$_ -match "IPv4" -and $_ -notmatch "192\.168\.56\." } |
    select-string -Pattern $regex -AllMatches | % { $_.Matches } | % { $_.Value }

Other approaches

I don't see any issues with you non-working version but I would like to offer an improvement to you select-string's since you don't exactly need to use more than one with some minor changes.

$ipAddressPrefix = [regex]::Escape("169.254.125.")
$ipaddresses = ipconfig | select-string "IPv4.*?: (?!$ipAddressPrefix).*" | % { $_.Matches } | % {$_.Value.Split(": ")[-1]}

# Pop up an alert window with the IP address
$wshell = New-Object -ComObject Wscript.Shell
$wshell.Popup($ipaddresses,0,"Done",0x1)

Lets take the first 3 octets that you are trying to not match and make an escaped matching string. Then using a negative lookahead we match the lines containing "IPv4" but that don't have the $ipAddressPrefix following the colon.

Then, using all matches returned we split the string on the colon and space to get the IpAddresses matched. Again, note that it is very possible to get more than one address returned in this way.

Preferntial Method

Parsing string to get this information is fine and dandy but you can get it much easier using WMI and not having to working about things like leading and trailing spaces

$ipAddressPrefix = [regex]::Escape("169.254.125.")
$ipaddresses = gwmi Win32_NetworkAdapterConfiguration | Where { $_.IPAddress -and ($_.IPAddress -notmatch "$ipAddressPrefix|:") } |
    Select -Expand IPAddress

$ipaddresses should contain the same results using either code snippet. The Where clause is broken down into these two parts

  1. $_.IPAddress --> this would filter out empty strings and nulls
  2. $_.IPAddress -notmatch "$ipAddressPrefix|:" would ensure that the $ipAddressPrefix matches are ommited. You will also see the : which will omit the IPv6 addresses.