Selective VPN routing in Windows

September 26th 2025 PowerShell Networking Windows

I mostly work remotely and to gain access to many work resources, I need a VPN connection to my office. When you connect, all of your network traffic is routed through the VPN connection by default. This can be reconfigured so that only the traffic to your office network is routed through the VPN. If you also need to access other resources through your VPN, e.g., cloud-hosted services whitelisted for your office IP, you need to properly route traffic for those as well.

For me, it all started pretty simple, with one such service. I got its IP using the ping command and added a new network route. I could have used the route command, but I found it easier to use the New-NetRoute PowerShell cmdlet because it can resolve the network interface by its alias and I don't have to search for the index of my VPN interface on the route print output:

New-NetRoute -DestinationPrefix 13.227.180.4/32 -InterfaceAlias MyVPN -NextHop 192.168.2.1

With time, it got more complicated:

  • There were domain names with more than one IP address. I could get all of those with a single call to the Resolve-DnsName cmdlet instead of multiple ping calls. But I still had to call New-NetRoute for each IP.
  • The IP addresses of some domains changed with time. Now I had to keep track of those and every time, it happened, I had to remove the routes for the old IPs using the Remove-NetRoute cmdlet before adding new ones.

It all required too much effort to maintain and became error prone. It was time to write some scripts to simplify my life. After some thought and experimentation, I wrote the following PowerShell script and named it New-NetRouteForHostname.ps1:

param(
  [Parameter(Mandatory)]
  [string]$Hostname,
  [bool]$Persist = 0,
  [string]$InterfaceAlias = "MyVPN",
  [string]$NextHop = "192.168.2.1"
)

$addresses = Resolve-DnsName $Hostname | Where-Object {$_.Type -eq "A"}
foreach ($address in $addresses) {
  if ($Persist) {
    New-NetRoute -DestinationPrefix "$($address.IPAddress)/32" `
      -InterfaceAlias $InterfaceAlias -NextHop $NextHop
  } else {
    New-NetRoute -DestinationPrefix "$($address.IPAddress)/32" `
      -InterfaceAlias $InterfaceAlias -NextHop $NextHop -PolicyStore ActiveStore
  }
}

It first calls Resolve-DnsName to get all addresses, i.e., A records, for a domain name. It then calls New-NetRoute for each one to add a new network route for it. The script outputs the routes it added.

The $Persist parameter allows different handling for domain names based on whether their IPs are static or not:

  • For domain names with static IPs, I set it to $true, so that PolicyStore is not set and the route is persisted across reboots and VPN reconnections.
  • For domain names with IPs that change, I set it to $false. This sets the PolicyStore to ActiveStore so that the route isn't persisted.

The $InterfaceAlias and $NextHop parameters allow me to call the script for a different VPN connection. Their default values match my office VPN, so I can usually simply omit them:

New-NetRouteForHostname dynamic-ip.mydomain.net

Since the New-NetRoute requires admin privileges to run, the script must be run in an elevated PowerShell prompt, or using the sudo command:

sudo pwsh -Command New-NetRouteForHostname dynamic-ip.mydomain.net

I know that the script is far from perfect, but it's good enough for now. If needed, I can always improve it further.

Get notified when a new blog post is published (usually every Friday):

Copyright
Creative Commons License