Two Windows setups look alike in Settings and behave nothing alike. One is a copied VPN profile with an application trigger, so starting one executable brings the tunnel up. The other is a split-tunnel profile whose route list happens to cover what that application talks to. The first answers when the tunnel exists. The second answers which packets enter it, and only the second leaves the rest of the machine on the physical link while the app stays connected.

The trigger is a lifecycle rule, not a routing rule

Add-VpnConnectionTriggerApplication binds an executable path to a profile name. When that process starts, the Remote Access Connection Manager dials the profile, and when the trigger condition ends the connection can drop. Nothing in that exchange inspects sockets. The cmdlet needs an elevated shell, the full path to the binary, and -Force when you replace an existing entry. Confirm the association survived with Get-VpnConnectionTrigger instead of trusting the prompt; Microsoft's Add-VpnConnectionTriggerApplication reference lists the parameters and the examples.

Two limits explain most "the trigger does nothing" reports. Triggers are built around all-user profiles, so the profile scope is the first thing to settle, and the application path has to match the binary that actually runs, not a shortcut or a launcher that spawns a second process. The other limit is by design: on a network Windows classifies as trusted, the trigger stays idle, because the profile is meant to fire away from the office.

Add-VpnConnectionTriggerApplication -Name "APP_VPN" -ApplicationID "C:\Program Files\Vendor\app.exe" -Force
Get-VpnConnectionTrigger -Name "APP_VPN" | Format-List *
Get-VpnConnection -Name "APP_VPN" | Format-List Name,ConnectionStatus,SplitTunneling,AllUserConnection

Split tunneling decides what crosses the tunnel

With split tunneling off, Windows installs a class-based default route with a low metric on the VPN interface. Every socket follows it, including the browser and the update service, so a trigger in that configuration moves the whole machine for as long as the profile is connected. The trigger is still just a lifecycle rule; the blast radius came from the route.

With split tunneling on, the profile carries only the prefixes that the gateway pushes or that you add to the VPN interface. Everything else keeps using the physical default route. That is the arrangement people usually mean by "one app through a separate exit", and it is a routing decision from end to end.

Set-VpnConnection -Name "APP_VPN" -SplitTunneling $true
Get-VpnConnection -Name "APP_VPN" | Select-Object SplitTunneling
Get-NetRoute -InterfaceAlias "APP_VPN" | Sort-Object DestinationPrefix

Where a routed split stops isolating your app

The common failure is a destination that never appears in the tunnel's route list. An app that signs in against one address and then streams from a CDN, or resolves a dozen rotating endpoints, will put some connections on the VPN interface and the rest on Wi-Fi. IPv6 is a separate table, so a profile that pushes only IPv4 prefixes can look correct while the app's IPv6 traffic stays local.

Two smaller effects matter as much. Longest-prefix match wins, so a more specific on-link route, a /32 pushed for a management host, or a second adapter's subnet can beat the tunnel for exactly the address you care about. Resolution happens before routing as well: unless the profile's DNS servers or an NRPT rule cover the name, the app can resolve through the physical adapter and then dial an address that was never inside the tunneled prefixes.

Three shapes, and what each one buys

The table is the part I would tape to the wall, because the decision is about blast radius rather than features. If the app's destinations are stable prefixes you can list, the routed split gives isolation with the smallest footprint. If they are not, a default-route profile behind a VM or a second device keeps the separation honest at the cost of moving more than one process.

ShapeWhat starts the tunnelWhat enters itPick it when
Trigger plus routed splitProcess startOnly pushed or added prefixesThe app's endpoints are stable prefixes and the host should stay direct
Trigger plus default routeProcess startEverything, machine-wideYou need one exit for all traffic and accept the wider scope
Separate VM or deviceGuest boot or loginEverything inside that guestThe app's destinations rotate, or the host must keep its own public address

What none of these provides is socket-level binding on the host. The classic Windows VPN client has no rule that says this process must use that adapter; triggers and route filters live in different layers, and the layer that selects an interface is the route table. If a guide promises per-app enforcement from a copied profile alone, ask which route or which filter is doing the work.

Verify the pair in one pass

Test the lifecycle and the routing separately, in that order. Close the app, confirm the profile reports Disconnected, then start the app and check that the state changed. Only after the tunnel is up should you ask where a specific destination goes, and you should use the address the app actually dials rather than a guess.

$app = Get-Process app | Select-Object -First 1
Get-VpnConnection -Name "APP_VPN" | Select-Object ConnectionStatus
Get-NetTCPConnection -OwningProcess $app.Id -State Established | Select-Object RemoteAddress,RemotePort
Find-NetRoute -RemoteIPAddress 203.0.113.10

Find-NetRoute names the interface and next hop that would carry that destination. A physical alias there means the tunnel is up and irrelevant for that flow: check the pushed prefixes, then the more specific routes, then IPv6. If the state never changes at all, the trigger did not fire, and the executable path, the profile scope and the VPN client operational log are the three places to look.

Undo cleanly so the next test is honest

Remove in the reverse order you built: the application trigger first, then the profile. Deleting the profile while the trigger remains leaves a dangling association that makes the next attempt confusing, and Remove-VpnConnection -Force does not always clear a phonebook entry that was created outside the cmdlets.

Per-user entries live in the user's rasphone.pbk under the Roaming profile; all-user entries live under ProgramData. Check the store that matches the profile scope, reconnect the profile you kept, and confirm its routes and DNS still behave, so a later test measures the change you made rather than leftover state.

  • Tunnel starts on process start, not on login.
  • Only the intended prefixes appear on the VPN interface.
  • The app's real remote address resolves to the VPN alias.
  • No dangling trigger is left after the profile is removed.

འབྲེལ་བའི་རྩོམ་ཡིག