Public/network/New-NetworkRoute.ps1

#Requires -Version 5.1

function New-NetworkRoute {
    <#
    .SYNOPSIS
        Creates a new static IP route on one or more Windows computers.
    .DESCRIPTION
        Adds a static route to the routing table using the New-NetRoute cmdlet.
        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 network prefix in CIDR notation (e.g. '10.0.0.0/8', '192.168.1.0/24').
    .PARAMETER NextHop
        The IP address of the next hop gateway.
    .PARAMETER InterfaceIndex
        The index of the network interface to use. Use Get-NetAdapter to find interface indexes.
    .PARAMETER InterfaceAlias
        The alias of the network interface to use (e.g. 'Ethernet'). Alternative to InterfaceIndex.
    .PARAMETER RouteMetric
        The route metric (cost). Lower values have higher priority. Defaults to 0 (automatic).
    .PARAMETER AddressFamily
        The address family. Valid values: IPv4, IPv6. Automatically inferred from
        DestinationPrefix if not specified.
    .EXAMPLE
        New-NetworkRoute -DestinationPrefix '10.10.0.0/16' -NextHop '192.168.1.1' -InterfaceAlias 'Ethernet'
 
        Creates a static route to 10.10.0.0/16 via gateway 192.168.1.1 on the local machine.
    .EXAMPLE
        New-NetworkRoute -DestinationPrefix '172.16.0.0/12' -NextHop '10.0.0.1' -InterfaceIndex 4 -RouteMetric 100
 
        Creates a static route with a specific metric and interface index.
    .EXAMPLE
        'SRV01', 'SRV02' | New-NetworkRoute -DestinationPrefix '10.10.0.0/16' -NextHop '192.168.1.1' -InterfaceAlias 'Ethernet'
 
        Creates the same static route on two remote servers via pipeline.
    .OUTPUTS
    PSWinOps.NetworkRoute
        The newly created 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/new-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 = $true)]
        [ValidateNotNullOrEmpty()]
        [string]$NextHop,

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

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

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

        [Parameter(Mandatory = $false)]
        [ValidateSet('IPv4', 'IPv6')]
        [string]$AddressFamily
    )

    begin {
        Write-Verbose "[$($MyInvocation.MyCommand)] Starting new network route creation"
        $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,
                [string]$QNextHop,
                [int]$QInterfaceIndex,
                [string]$QInterfaceAlias,
                [int]$QRouteMetric,
                [string]$QAddressFamily
            )

            $newParams = @{
                DestinationPrefix = $QDestinationPrefix
                NextHop           = $QNextHop
                Confirm           = $false
                ErrorAction       = 'Stop'
            }

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

            if ($QRouteMetric -gt 0) {
                $newParams['RouteMetric'] = $QRouteMetric
            }

            if ($QAddressFamily) {
                $newParams['AddressFamily'] = $QAddressFamily
            }

            $route = New-NetRoute @newParams

            [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 via $NextHop on '$targetComputer'"

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

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

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

                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 new network route creation"
    }
}