Summary: Microsoft support engineer James Kehr talks about using Windows PowerShell to change IP behavior by implementing SkipAsSource.
Microsoft Scripting Guy, Ed Wilson, is here. Today, we have guest blogger James Kehr, a support engineer here at Microsoft.
Take it away, James …
When James was asked to write a guest blog post for the “Hey, Scripting Guy! Blog”, he felt honored. The Scripting Guy asked James to write in the first person so James immediately started his post in the third-person, which, in James’ opinion, is the absolute worse point of view to write a blog post in. Maybe Ed should have tried reverse-reverse psychology to prevent this from happening. Which James supposes would be normal psychology, but he’s not quite sure.
James will now perform one of the most abhorrent violations of the literary world and switch the point of view within the same volume of work. Please do not try this at home. Unless you’re Patrick Rothfuss, then it’s okay, because that means you’re an awesome writer who knows how to get away with it.
Since I, being James in the first person, have never posted on the “Hey, Scripting Guy! Blog” before I’ll start with a short introduction. I have worked in IT for about fifteen years, starting out in desktop support and moving up to server administration, infrastructure design and support, and now as a networking support engineer for Microsoft’s commercial technical support division. Over the years I have learned the value of having really good tools. Sometimes those tools are a properly milled Phillips head screwdriver to take apart a laptop, and sometimes they are a script.
You can do an amazing amount of work with scripts. Sure it takes some time to write scripts, but with the wealth of information and pre-written code that resides within that amazing series of tubes called the Internet, it doesn’t take nearly as much time as it used to. And the amount of time it saves you is astonishing. In a past work life, it took two and half admin hours to install and configure SQL Server, until a coworker and I automated the company standard SQL installation by using Windows PowerShell. Once scripted, it took about five minutes of admin time. That is not an exaggeration either.
Do IP addresses skip when no one is looking?
An admin intensive task that I have recently scripted involves the primary IP address emulation in Windows Vista and Windows Server 2008, and newer, using the SkipAsSource option. For those still on Windows XP and Windows Server 2003, let me explain. There is no longer a primary IP address. Though I think you got that part already. When you have multiple IP addresses on a Windows Server 2008 (and later versions) network adapter, the address you enter on the General tab of the IPv4 properties, which was traditionally the primary IP address, is no longer guaranteed to be the IP address used by the computer to communicate on the network. All IP addresses are registered in DNS by default and any one of them can be used by the host. This usually does not cause a problem, unless you have very strict firewall controls. Especially on the network parameter.
If you need that old primary IP behavior, you have to add the IP addresses by using the SkipAsSource option. Which you can’t do in the UI. You must use netsh, or Windows PowerShell 3.0, if you’re using Windows Server 2012 or Windows 8. And if the IP address is already there, you may have to remove it first, then add it back with SkipAsSource set. This isn’t a problem if you only have two or three IP addresses on the server. When you get to about forty or fifty, like on a web server, the task tends to make server admins very grumpy, very fast.
Windows PowerShell to the rescue! [Queue dramatic super hero music.]
I won’t go into why this behavior changed as other people have explained it in great detail. Instead, I’ll give you a few links to well-written articles by people who know how to stick to a single point of view and know a great deal about the topic.
http://support.microsoft.com/kb/975808
The basics of how SkipAsSource works is easy. If SkipAsSource is set to True, then that IP address will not be registered in DNS and will not be used for host initiated outgoing communications, unless the application specifically binds to it. Setting SkipAsSource to False means the IP is registered in DNS and is used for generic host communications. Setting SkipAsSource to False on a single IP address emulates the primary IP address behavior.
You can view the SkipAsSource state of the IP addresses on a computer by using one of the following commands:
netsh interface ipv4 show ipaddresses level=verbose
PowerShell 3.0 on 8/2012+:
Get-NetIPAddress
How to skip with Windows PowerShell 2.0
Back in the old days, which was three months ago as I write this, when there was only Windows PowerShell 2.0, the only way to add an IP address by using SkipAsSource was netsh. No big deal, you can use netsh in Windows PowerShell. There are some things we need to know before we get to the netsh command, though.
The first thing we need to know is what network interface to use. To super simplify this process, we’ll just store that in a variable.
$netInterface = "Local Area Connection"
Next, we need to know what our “primary IP address” is and its subnet mask. For the purposes of this example, I’m going to use 10.0.0.2 as the first, and primary, IP of a range of fifty addresses ending in 10.0.0.51, with the subnet mask 255.0.0.0 (/8 in CIDR notation).
$primaryIP = "10.0.0.2"
$primarySNM = "255.0.0.0"
This is normally the point where I go on a meandering tangent about validating input. I’ll dispense with the meandering and just say, if you don’t trust your users, something you should never do, you can validate an IP address by doing this, which will return a True or False Boolean value:
$isIpValid = [System.Net.IPAddress]::tryparse([string]$primaryIP, [ref]"1.1.1.1")
The last bit of information we need is a list of IP addresses on the interface. This is where things get kind of messy. You see, to add an IP address with netsh, you need the subnet mask. While my example would all use the same subnet mask, in the real world, this may not be the case. To prevent IP address to subnet mask mismatches, which would render that IP address useless, we need to use WMI to get matching sets.
To further complicate things, the WMI method lists all addresses on the interface, including IPv6 addresses. To keep things simple, I am only dealing with IPv4 addresses, so IPv6 needs to get filtered out. And finally, I want to keep the code as efficient as possible which means the primary IP address should be filtered out so I don’t need to use an if-statement in the upcoming foreach-loop to check whether the current IP is the primary.
To accomplish all of these goals, we need to perform three steps. First, get the Win32_NetworkAdapter object that matches the $netInterface.
$netAdapter = Get-WmiObject Win32_NetworkAdapter -Filter "NetConnectionID = '$netInterface'"
Second, get the corresponding network adapter configuration. We do this by matching the index number of $netAdapter to the index of the corresponding Win32_NetworkAdapterConfiguration object. This step gets us the IP addresses on the interface.
$netNAC = Get-WmiObject Win32_NetworkAdapterConfiguration -Filter "Index = '$($netAdapter.Index)'"
Finally, we load the matching sets of IP addresses to subnet masks into an array of PSObjects. The primary IP address and IPv6 addresses are filtered out in the process by using a Where-Object cmdlet and a couple of comparison operators.
0..($netNAC.IPAddress.count - 1) | Where-Object {$netNAC.IPAddress[$_].ToString() -ne $primaryIP -and $netNAC.IPAddress[$_].ToString() -like "*.*.*.*"} | ForEach-Object {
$temp = New-Object PSObject -Property @{
IPAddress = $netNAC.IPAddress[$_].ToString()
IPSubnet = $netNAC.IPSubnet[$_].ToString()
}
$IPs += $temp
}
Smashing! Now on to the core of the script, implementing SkipAsSource. To keep the code efficient, I do this in two parts: set SkipAsSource=false for the primary IP and set SkipAsSource=true for the secondary IPs by using a foreach-loop.
# delete the primary IP. Needed to ensure the primary IP is set to SkipAsSource=false so at least one IP is used for outbound traffic.
# Since I can't tell whether SkipAsSource is set in Windows Server 2008 or Windows Vista, I run this as a precaution in case this is set to True for any reason.
netsh int ipv4 delete address "$netInterface" $primaryIP
# add the IP with SkipAsSource set to False
netsh int ipv4 add address "$netInterface" $primaryIP $primarySNM skipassource=false
Last, but not least, we delete and add the rest of the IPs on the interface with SkipAsSource set to True.
# loop through each IP address in the list
foreach ($ip in $IPs) {
# delete the IP
Invoke-Expression "netsh int ipv4 delete address `"$interface`" $($ip.IPAddress)"
# add the IP with SkipAsSource set to True
Invoke-Expression "netsh int ipv4 add address `"$interface`" $($ip.IPAddress) $($ip.IPSubnet) skipassource`=true"
}
Pretty simple and straightforward, don’t you think? Only if you’re The Scripting Guy!, which I am not. This took quite a bit of time and research to figure out. If only Windows PowerShell 3.0 had been released with Windows Vista and Windows Server 2008 …
How to dance with Windows PowerShell 3.0
Do you like magic tricks? I do. Magic tricks are cool. This is my Windows PowerShell magic trick. When I speak (or rather type) the magic word, all that scary-looking Windows PowerShell 2.0 code will disappear and be replaced with the Windows PowerShell 3.0 version of the same code.
Are you ready?
Alakazam!
# set some constants
$primaryIP = "10.0.0.2"
$netInterface = "Ethernet"
# get all the IP addresses on the interface, filtered by IPv4 and excluding
# the $primaryIP
[array]$IPs = Get-NetIPAddress -InterfaceAlias $netInterface | Where-Object {$_.AddressFamily -eq "IPv4" -and $_.IPAddress -ne $primaryIP}
# set primary IP SkipAsSource to false
Set-NetIPAddress -IPAddress $primaryIP -InterfaceAlias $netInterface -SkipAsSource $false
# set other IP addresses with SkipAsSource equal to true
Set-NetIPAddress -IPAddress $IPs.IPAddress -InterfaceAlias $netInterface -SkipAsSource $true
Isn’t progress wonderful? No deleting IP addresses. No mucking around with WMI. No crazy loops or extra steps. Just a neat, simple, small set of code. And it took me less than fifteen minutes to write and test it.
The three-step Windows PowerShell 2.0 method of gathering the IP addresses on an interface is done away with, replaced by a single line using the new Get-NetIPAddress cmdlet. I don’t have to create an array of matching addresses to subnet masks because the output of this command is an array of objects that already contain that pairing, instead of two separate arrays that need to be matched up like WMI has.
Get-NetIPAddress -InterfaceAlias $netInterface | Where-Object {$_.AddressFamily -eq "IPv4" -and $_.IPAddress -ne $primaryIP}
With the addition of Set-NetIPAddress, you don’t have to delete the IP and then add it back anymore either. This cmdlet simply updates the IP address in a single clean step. This also means I don’t need the matching subnet masks at all, because I’m updating, not re-adding. Since the IPAddress parameter accepts string arrays, I just pass that by using the new Windows PowerShell 3.0 automatic foreach feature ($IPs.IPAddress instead of $($IPs | %{$_.IPAddress})).
Set-NetIPAddress -IPAddress $IPs.IPAddress -InterfaceAlias $netInterface -SkipAsSource $true
Now comes the “but.” I hate this part.
But this only works with Windows 8 and Windows Server 2012. Even if you install Windows PowerShell 3.0 on Windows 7 or Windows Server 2008 R2, you do not get the NetTCPIP module where the NetIPAddress family of cmdlets comes from. Which means … you now have one more good excuse to upgrade to Windows 8 and Windows Server 2012!
Dancing and skipping and having fun
See, that wasn’t so scary. I hope you had as much fun reading this blog post as I did writing it. More than anything else I hope you learned something new about Windows PowerShell and Windows networking.
Change can be scary, but sometimes it works out for the best. Windows PowerShell 3.0 is like that. Windows PowerShell in general is like that. The more you learn, the better you can be at your Windows administration tasks. Trust me, I’ve done a lot of different IT jobs, and I love Windows PowerShell. And to top it off, it gets better with each release.
So stop reading and go script. You’ll be glad you did.
~James
Thank you, James, for an excellent article about a great topic!
I invite you to follow me on Twitter and Facebook. If you have any questions, send email to me at scripter@microsoft.com, or post your questions on the Official Scripting Guys Forum. See you tomorrow. Until then, peace.
Ed Wilson, Microsoft Scripting Guy