Public/network/Set-NetworkRoute.ps1

#Requires -Version 5.1

function Set-NetworkRoute {
    <#
    .SYNOPSIS
        Modifies an existing IP route on one or more Windows computers.
    .DESCRIPTION
        Updates properties of an existing route in the routing table using the
        Set-NetRoute cmdlet. Can modify the route metric of a route identified by
        its destination prefix and interface.
 
        Requires Administrator privileges.
 
        For remote computers, the operation is executed via Invoke-Command, which
        requires WinRM / WS-Man enabled on the target.
    .PARAMETER ComputerName
        One or more computer names to configure. Accepts pipeline input by value and
        by property name. Defaults to the local machine ($env:COMPUTERNAME).
    .PARAMETER Credential
        Optional PSCredential for authenticating to remote machines. Ignored for
        local machine operations.
    .PARAMETER DestinationPrefix
        The destination prefix of the route to modify (e.g. '10.0.0.0/8').
    .PARAMETER InterfaceIndex
        The interface index that identifies the route to modify.
    .PARAMETER InterfaceAlias
        The interface alias that identifies the route to modify. Alternative to InterfaceIndex.
    .PARAMETER NextHop
        Optional: the next hop address to narrow route selection when multiple routes
        share the same destination prefix and interface.
    .PARAMETER RouteMetric
        The new route metric value to set.
    .EXAMPLE
        Set-NetworkRoute -DestinationPrefix '10.10.0.0/16' -InterfaceAlias 'Ethernet' -RouteMetric 50
 
        Changes the metric of the route to 10.10.0.0/16 on the Ethernet interface to 50.
    .EXAMPLE
        Set-NetworkRoute -DestinationPrefix '10.10.0.0/16' -InterfaceIndex 4 -RouteMetric 200
 
        Changes the metric using interface index instead of alias.
    .EXAMPLE
        'SRV01', 'SRV02' | Set-NetworkRoute -DestinationPrefix '10.10.0.0/16' -InterfaceAlias 'Ethernet' -RouteMetric 50
 
        Modifies the route metric on two remote servers via pipeline.
    .OUTPUTS
    PSWinOps.NetworkRoute
        The modified route details.
    .NOTES
        Author: Franck SALLET
        Version: 1.0.0
        Last Modified: 2026-03-20
        Requires: PowerShell 5.1+ / Windows only
        Permissions: Administrator privileges required
        Remote: Requires WinRM / WS-Man enabled on target machines
    .LINK
    https://docs.microsoft.com/en-us/powershell/module/nettcpip/set-netroute
    #>

    [CmdletBinding(SupportsShouldProcess, ConfirmImpact = 'Medium')]
    [OutputType('PSWinOps.NetworkRoute')]
    param(
        [Parameter(Mandatory = $false,
            ValueFromPipeline = $true,
            ValueFromPipelineByPropertyName = $true)]
        [ValidateNotNullOrEmpty()]
        [Alias('CN', 'Name', 'DNSHostName')]
        [string[]]$ComputerName = $env:COMPUTERNAME,

        [Parameter(Mandatory = $false)]
        [PSCredential]$Credential,

        [Parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [string]$DestinationPrefix,

        [Parameter(Mandatory = $false)]
        [ValidateRange(1, 999999)]
        [int]$InterfaceIndex,

        [Parameter(Mandatory = $false)]
        [ValidateNotNullOrEmpty()]
        [string]$InterfaceAlias,

        [Parameter(Mandatory = $false)]
        [ValidateNotNullOrEmpty()]
        [string]$NextHop,

        [Parameter(Mandatory = $true)]
        [ValidateRange(0, 9999)]
        [int]$RouteMetric
    )

    begin {
        Write-Verbose "[$($MyInvocation.MyCommand)] Starting network route modification"
        $localNames = @($env:COMPUTERNAME, 'localhost', '.')
        $hasCredential = $PSBoundParameters.ContainsKey('Credential')

        if (-not $PSBoundParameters.ContainsKey('InterfaceIndex') -and -not $PSBoundParameters.ContainsKey('InterfaceAlias')) {
            $PSCmdlet.ThrowTerminatingError(
                [System.Management.Automation.ErrorRecord]::new(
                    [System.ArgumentException]::new('Either InterfaceIndex or InterfaceAlias must be specified.'),
                    'MissingInterfaceParameter',
                    [System.Management.Automation.ErrorCategory]::InvalidArgument,
                    $null
                )
            )
        }

        $queryScriptBlock = {
            param(
                [string]$QDestinationPrefix,
                [int]$QInterfaceIndex,
                [string]$QInterfaceAlias,
                [string]$QNextHop,
                [int]$QRouteMetric
            )

            # Build params to identify the route
            $setParams = @{
                DestinationPrefix = $QDestinationPrefix
                RouteMetric       = $QRouteMetric
                Confirm           = $false
                ErrorAction       = 'Stop'
            }

            if ($QInterfaceIndex -gt 0) {
                $setParams['InterfaceIndex'] = $QInterfaceIndex
            } elseif ($QInterfaceAlias) {
                $setParams['InterfaceAlias'] = $QInterfaceAlias
            }

            if ($QNextHop) {
                $setParams['NextHop'] = $QNextHop
            }

            Set-NetRoute @setParams

            # Retrieve the updated route
            $getParams = @{
                DestinationPrefix = $QDestinationPrefix
                ErrorAction       = 'Stop'
            }
            if ($QInterfaceIndex -gt 0) {
                $getParams['InterfaceIndex'] = $QInterfaceIndex
            } elseif ($QInterfaceAlias) {
                $getParams['InterfaceAlias'] = $QInterfaceAlias
            }

            $route = Get-NetRoute @getParams | Select-Object -First 1

            [PSCustomObject]@{
                DestinationPrefix = $route.DestinationPrefix
                NextHop           = $route.NextHop
                InterfaceAlias    = $route.InterfaceAlias
                InterfaceIndex    = $route.InterfaceIndex
                RouteMetric       = $route.RouteMetric
                AddressFamily     = if ($route.AddressFamily -eq 2) {
                    'IPv4'
                } elseif ($route.AddressFamily -eq 23) {
                    'IPv6'
                } else {
                    [string]$route.AddressFamily
                }
                Protocol          = [string]$route.Protocol
                Store             = [string]$route.Store
            }
        }
    }

    process {
        foreach ($targetComputer in $ComputerName) {
            try {
                $isLocal = $localNames -contains $targetComputer
                $timestamp = Get-Date -Format 'o'
                $shouldProcessTarget = "Route $DestinationPrefix on '$targetComputer' — set metric to $RouteMetric"

                Write-Verbose "[$($MyInvocation.MyCommand)] Modifying route on '$targetComputer' (local: $isLocal)"

                if (-not $PSCmdlet.ShouldProcess($shouldProcessTarget, 'Modify network route')) {
                    continue
                }

                $queryArgs = @(
                    $DestinationPrefix
                    $(if ($PSBoundParameters.ContainsKey('InterfaceIndex')) {
                            $InterfaceIndex
                        } else {
                            0
                        })
                    $(if ($PSBoundParameters.ContainsKey('InterfaceAlias')) {
                            $InterfaceAlias
                        } else {
                            $null
                        })
                    $(if ($PSBoundParameters.ContainsKey('NextHop')) {
                            $NextHop
                        } else {
                            $null
                        })
                    $RouteMetric
                )

                if ($isLocal) {
                    $rawResult = & $queryScriptBlock @queryArgs
                } else {
                    $invokeParams = @{
                        ComputerName = $targetComputer
                        ScriptBlock  = $queryScriptBlock
                        ArgumentList = $queryArgs
                        ErrorAction  = 'Stop'
                    }
                    if ($hasCredential) {
                        $invokeParams['Credential'] = $Credential
                    }
                    $rawResult = Invoke-Command @invokeParams
                }

                [PSCustomObject]@{
                    PSTypeName        = 'PSWinOps.NetworkRoute'
                    ComputerName      = $targetComputer
                    DestinationPrefix = $rawResult.DestinationPrefix
                    NextHop           = $rawResult.NextHop
                    InterfaceAlias    = $rawResult.InterfaceAlias
                    InterfaceIndex    = $rawResult.InterfaceIndex
                    RouteMetric       = $rawResult.RouteMetric
                    AddressFamily     = $rawResult.AddressFamily
                    Protocol          = $rawResult.Protocol
                    Store             = $rawResult.Store
                    Timestamp         = $timestamp
                }
            } catch {
                Write-Error "[$($MyInvocation.MyCommand)] Failed on '$targetComputer': $_"
            }
        }
    }

    end {
        Write-Verbose "[$($MyInvocation.MyCommand)] Completed network route modification"
    }
}