In the latest versions in the Windows 10 client operating system, Microsoft already includes a “Default Virtual Switch”, which allows you to use Hyper-V NAT Networking, without doing any configuration changes.
If you want to create an additional VM Switch which uses NAT on Windows 10, or you want to use the Hyper-V NAT VM Switch on Windows Server, you can create it with Powershell.
Create Hyper-V NetNat Switch
# Name of New Switch
$NETNatSwitch = 'FABRICNETNAT'
# Network IP Address Space
$NATNetwork = '192.168.20.0'
# Default Gateway Address
$NATRouterAddress = '192.168.20.1'
# Bitmask of subnetmask i.e 24 = 255.255.255.0
$NATPrefixLength = '24'
New-VMSwitch -SwitchName $NETNatSwitch -SwitchType Internal
$NatSwitch = Get-NetAdapter | Where-Object Name -Like "vEthernet ($NETNatSwitch)"
New-NetIPAddress -IPAddress $NATRouterAddress -PrefixLength 24 -InterfaceIndex $NatSwitch.InterfaceIndex
$NATNetworkFull = $NATNetwork + '/' + $NATPrefixLength
New-NetNat -Name $($NETNatSwitch + "-network") -InternalIPInterfaceAddressPrefix $NATNetworkFull
I have also made a powershell GUI to do the same thing with a little more checks that some values do not already exist or are incorrect.
Create NetNat switch with GUI
<#
Solution: Microsoft Hyper-V Tool
Purpose: Create New Swicth with NetNat GUI
Version: 2.0.1
Date: 12 Mars 2021
Author: Tomas Johansson
Twitter: @deploymentnoob
Web: https://www.4thcorner.net
This script is provided "AS IS" with no warranties, confers no rights and
is not supported by the author
#>
#Add WPF and Windows Forms assemblies
try {
Add-Type -AssemblyName PresentationCore -ErrorAction Stop
Add-Type -AssemblyName PresentationFramework -ErrorAction Stop
Add-Type -AssemblyName system.windows.forms -ErrorAction Stop
}
catch {
Throw "Failed to load Windows Presentation Framework assemblies."
}
# Check for elevation
If (-NOT ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole( `
[Security.Principal.WindowsBuiltInRole] "Administrator")) {
$ButtonType = [System.Windows.MessageBoxButton]::OK
$MessageboxTitle = "Aborting script..."
$Messageboxbody = "Oupps, you need to run this script from an elevated PowerShell prompt! Please start the PowerShell prompt as an Administrator and re-run the script."
$MessageIcon = [System.Windows.MessageBoxImage]::Exclamation
[System.Windows.MessageBox]::Show($Messageboxbody,$MessageboxTitle,$ButtonType,$messageicon)
Exit
}
if(!(get-module -ListAvailable -Name "hyper-v")) {
$ButtonType = [System.Windows.MessageBoxButton]::OK
$MessageboxTitle = "Hyper-V module not found"
$Messageboxbody = "Please run this tool on a server that has the Hyper-V management tools installed"
$MessageIcon = [System.Windows.MessageBoxImage]::Exclamation
[System.Windows.MessageBox]::Show($Messageboxbody,$MessageboxTitle,$ButtonType,$messageicon)
Exit
}
# Windows GUIin XAML
[xml]$XAML = @'
<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="New Hyper-V NetNat Switch" Height="380" Width="450" Topmost="True" ResizeMode="NoResize">
<Grid Height="350" Width="450" HorizontalAlignment="Left" Margin="0,-1,-6,2">
<Label x:Name="Label_SwitchName" Content="Switch Name:" HorizontalAlignment="Left" Margin="20,15,0,0" VerticalAlignment="Top"/>
<TextBox x:Name="TextBox_SwitchName" HorizontalAlignment="Left" Height="23" Margin="25,37,0,0" TextWrapping="Wrap" Text="" VerticalAlignment="Top" Width="300" TabIndex="0"/>
<Label x:Name="Label_IPAdress_Gateway" Content="IP Adress of Gateway:" HorizontalAlignment="Left" Margin="20,59,0,0" VerticalAlignment="Top"/>
<TextBox x:Name="TextBox_IPAdressGateway" HorizontalAlignment="Left" Height="23" Margin="25,82,0,0" TextWrapping="Wrap" Text="" VerticalAlignment="Top" Width="300" TabIndex="1"/>
<Label x:Name="Label_PrefixxLength" Content="Prefix:" HorizontalAlignment="Left" Margin="20,103,0,0" VerticalAlignment="Top"/>
<TextBox x:Name="TextBox_PrefixLength" HorizontalAlignment="Left" Height="23" Margin="25,127,0,0" TextWrapping="Wrap" Text="" VerticalAlignment="Top" Width="50" TabIndex="2"/>
<Label x:Name="Label_NewSwitchName" Content="Switch Name:" HorizontalAlignment="Left" Margin="76,160,0,0" VerticalAlignment="Top"/>
<Label x:Name="Label_NewSwitchName_Result" Content="" HorizontalAlignment="Left" Margin="155,160,0,0" VerticalAlignment="Top"/>
<Label x:Name="Label_Gateway_IPAdress" Content="Gateway IP Address:" HorizontalAlignment="Left" Margin="42,200,0,0" VerticalAlignment="Top"/>
<Label x:Name="Label_Gateway_IPAddress_Result" Content="" HorizontalAlignment="Left" Margin="155,201,0,0" VerticalAlignment="Top"/>
<Label x:Name="Label_Network" Content="Network:" HorizontalAlignment="Left" Margin="100,180,0,0" VerticalAlignment="Top"/>
<Label x:Name="Label_IPAddressSpace" Content="" HorizontalAlignment="Left" Margin="155,180,0,0" VerticalAlignment="Top"/>
<Label x:Name="Label_InternalAdressPrefix" Content="Internal IP Adress Prefix:" HorizontalAlignment="Left" Margin="21,220,0,0" VerticalAlignment="Top"/>
<Label x:Name="Label_InternalAdressPrefix_Result" Content="" HorizontalAlignment="Left" Margin="155,220,0,0" VerticalAlignment="Top"/>
<Label x:Name="Label_Subnetmask" Content="Subnetmask:" HorizontalAlignment="Left" Margin="81,240,0,0" VerticalAlignment="Top"/>
<Label x:Name="Label_InternalAdressPrefix_Calculated" Content="" HorizontalAlignment="Left" Margin="156,240,0,0" VerticalAlignment="Top"/>
<Label x:Name="Label_Create_Information" Content="" HorizontalAlignment="Center" Margin="20,279,0,0" VerticalAlignment="Top"/>
<Button x:Name="Button_CreateNetNat" Content="Create" HorizontalAlignment="Left" Margin="260,320,0,0" VerticalAlignment="Top" Width="75" TabIndex="3"/>
<Button x:Name="Button_Cancel" Content="Cancel" HorizontalAlignment="Left" Margin="350,320,0,0" VerticalAlignment="Top" Width="75" TabIndex="4"/>
</Grid>
</Window>
'@
# Create Hashtable and Runspace for GUI
$SyncHash = [hashtable]::Synchronized(@{})
$NewRunspace =[runspacefactory]::CreateRunspace()
$NewRunspace.ApartmentState = "STA"
$NewRunspace.ThreadOptions = "ReuseThread"
$NewRunspace.Open()
$NewRunspace.SessionStateProxy.SetVariable("syncHash",$SyncHash)
# Create the XAML reader using a new XML node reader
$XAMLReader = New-Object System.Xml.XmlNodeReader $XAML
$SyncHash.Window = [Windows.Markup.XamlReader]::Load($XAMLReader)
$XAML.SelectNodes("//*[@*[contains(translate(name(.),'n','N'),'Name')]]") |
ForEach-Object {
$SyncHash.Add($_.Name,$SyncHash.Window.FindName($_.Name))
}
$SyncHash.TextBox_SwitchName.Add_TextChanged({
$NewswitchName = $SyncHash.TextBox_SwitchName.Text
$ExistName = Get-VMSwitch | Where-Object {$_.Name -like "$NewswitchName"} | Select-Object -ExpandProperty Name
If ([string]::IsNullOrEmpty($ExistName)) {
$SwitchExist = $False
}
Else {
$SwitchExist = $True
}
$SyncHash.Window.Dispatcher.Invoke(
[action]{
If ($SwitchExist -eq $True) {
$SyncHash.Label_NewSwitchName_Result.Foreground = "Red"
$SyncHash.Label_NewSwitchName_Result.Content = "Switch with that name already exist!"
$SyncHash.Button_CreateNetNat.IsEnabled = $False
}
Else {
$SyncHash.Label_NewSwitchName_Result.Foreground = "Black"
$SyncHash.Label_NewSwitchName_Result.Content = $SyncHash.TextBox_SwitchName.Text
$SyncHash.Button_CreateNetNat.IsEnabled = $True
}
}
)
})
$SyncHash.TextBox_PrefixLength.Add_TextChanged({
$Bitmask = $SyncHash.TextBox_PrefixLength.Text
If (!([string]::IsNullOrEmpty($Bitmask))) {
$Result = Test-Bitmask $Bitmask
}
If ($Result -eq $true) {
$SubnetMask = ConvertTo-IPv4MaskString $SyncHash.TextBox_PrefixLength.Text
}
$ValidSubnet = Test-IPv4MaskString $SubnetMask
$SyncHash.Window.Dispatcher.Invoke(
[action]{
$SyncHash.Label_InternalAdressPrefix_Result.Content = $SyncHash.TextBox_PrefixLength.Text
If ($ValidSubnet -eq $true) {
$SyncHash.Label_InternalAdressPrefix_Result.Foreground = "Black"
$SyncHash.Label_InternalAdressPrefix_Calculated.Foreground = "Black"
$SyncHash.Label_InternalAdressPrefix_Calculated.Content = $SubnetMask
$SyncHash.Button_CreateNetNat.IsEnabled = $True
}
Else {
$SyncHash.Label_InternalAdressPrefix_Result.Foreground = "Red"
$SyncHash.Label_InternalAdressPrefix_Calculated.Foreground = "Red"
$SyncHash.Label_InternalAdressPrefix_Calculated.Content = "Not Valid subnetmask"
$SyncHash.Button_CreateNetNat.IsEnabled = $False
}
}
)
})
$SyncHash.TextBox_IPAdressGateway.Add_TextChanged({
$GWIPAddress = $SyncHash.TextBox_IPAdressGateway.Text
$VaildIPAdress = Test-GateWayIPAdress $GWIPAddress
If ($VaildIPAdress -eq $true) {
$GatewayIPExist = Test-NetIPAdress $GWIPAddress
If ($GatewayIPExist -eq $False) {
$IPAddressSpace = Resolve-IPv4NetworkSpace $GWIPAddress
$SyncHash.Label_Gateway_IPAddress_Result.Foreground = "Black"
$ResultIPAdress = $GWIPAddress
}
Else {
$SyncHash.Label_Gateway_IPAddress_Result.Foreground = "Red"
$ResultIPAdress = "IP Adress already exist!"
$IPAddressSpace = "-"
}
}
$SyncHash.Window.Dispatcher.Invoke(
[action]{
If ($VaildIPAdress -eq $true) {
$SyncHash.Label_Gateway_IPAddress_Result.Content = $ResultIPAdress
$SyncHash.Label_IPAddressSpace.Content = $IPAddressSpace
$SyncHash.Button_CreateNetNat.IsEnabled = $True
}
Else {
$SyncHash.Label_Gateway_IPAddress_Result.Content = $ResultIPAdress
$SyncHash.Button_CreateNetNat.IsEnabled = $False
}
If ($GatewayIPExist -eq $True) {
$SyncHash.Button_CreateNetNat.IsEnabled = $False
}
Else {
$SyncHash.Button_CreateNetNat.IsEnabled = $True
}
}
)
})
# Close Dialog Window
$SyncHash.button_cancel.add_click({
$SyncHash.Window.Close()
})
# Creat NetNat switch
$SyncHash.Button_CreateNetNat.add_click({
$SwitchName = $SyncHash.TextBox_SwitchName.text
$NetworkGatewayIPAddress = $SyncHash.TextBox_IPAdressGateway.Text
$NetworkPrefix = $SyncHash.TextBox_PrefixLength.Text
# Network Adress Space
$NetworkIPAddressSpace = Resolve-IPv4NetworkSpace $NetworkGatewayIPAddress
# Check if NetNat already exist
$NetNatExist = Test-NetNat $NetworkIPAddressSpace $NetworkPrefix
If ($NetNatExist -eq $False) {
# Create
$NetNatCreated = New-NetNatNetwork -SwitchName $SwitchName -GatewayIPAddress $NetworkGatewayIPAddress -IPAddressSpace $NetworkIPAddressSpace -PrefixLengt $NetworkPrefix
If ($NetNatCreated -eq $True) {
$SyncHash.Label_Create_Information.Foreground = "Black"
$ResultText = "NetNat created for switch: " + $SwitchName
}
Else {
$SyncHash.Label_Create_Information.Foreground = "Red"
$ResultText = "Failed to Create NetNat for switch: " + $SwitchName
}
}
Else {
$ResultText = "NetNat: " + $NetNatExist + " Exist already"
}
$SyncHash.Window.Dispatcher.Invoke(
[action]{
$SyncHash.Label_Create_Information.Content = $ResultText
}
)
})
Function New-NetNatNetwork {
Param (
[string]$SwitchName,
[string]$GatewayIPAddress,
[string]$IPAddressSpace,
[byte]$PrefixLengt
)
BEGIN {
$Retval = $Null
$NetNatName = $SwitchName + "-Network"
$InterfaceAddressPrefix = $IPAddressSpace + "/" + $PrefixLengt
}
PROCESS {
Try {
$Retval = (New-VMSwitch -SwitchName $SwitchName -SwitchType Internal -ErrorAction Stop).Name
If (!([string]::IsNullOrEmpty($Retval))) {
Try {
$InterfaceIndex = $Null
$InterfaceIndex = (Get-NetAdapter | Where-Object {$_.Name -Like "vEthernet ($SwitchName)"}).InterfaceIndex
If (!([string]::IsNullOrEmpty($InterfaceIndex))) {
Try {
$Retval = $Null
$Retval = (New-NetIPAddress -IPAddress $GatewayIPAddress -PrefixLength $PrefixLengt -InterfaceIndex $InterfaceIndex -ErrorAction Stop).Count
If (!([string]::IsNullOrEmpty($Retval))) {
Try {
$Retval = $Null
Try {
$Null = New-NetNat -Name $NetNatName -InternalIPInterfaceAddressPrefix $InterfaceAddressPrefix -ErrorAction Stop
$Result = $True
}
Catch {
$Result = $False
}
}
Catch {
$Result = $False
}
}
Else {
$Result = $False
}
}
Catch {
$Result = $False
}
}
Else {
$Result = $False
}
}
Catch {
$Result = $False
}
}
Else {
$Result = $False
}
}
Catch {
$Result = $False
}
}
END {
Return $Result
}
}
Function Test-NetNat {
Param (
[String]$NetworkSpace,
[string]$NetworkPrefix
)
BEGIN{
$Retval = $Null
$InternalIPInterfaceAddressPrefix = $NetworkSpace + "/" + $NetworkPrefix
}
PROCESS{
Try {
$Retval = Get-NetNat | Where-Object {$_.InternalIPInterfaceAddressPrefix -match $InternalIPInterfaceAddressPrefix} | Select-Object -ExpandProperty Name -ErrorAction Stop
If(!([string]::IsNullOrEmpty($Retval))) {
$Retval = $True
}
Else {
$Retval = $False
}
}
Catch {
$Retval = $False
}
}
END{
Return $Retval
}
}
Function Test-NetIPAdress {
Param (
[string]$GatewayIPAddress
)
BEGIN {
}
PROCESS {
Try {
$Null = Get-NetIPAddress -IPAddress $GatewayIPAddress -ErrorAction Stop
$Retval = $true
}
Catch {
$Retval = $False
}
}
END {
Return $Retval
}
}
Function Test-GateWayIPAdress {
<#
.SYNOPSIS
Tests whether an IPv4 Gateway Adress string (e.g., "192.168.10.1") is valid.
.DESCRIPTION
ests whether an IPv4 Gateway Adress string (e.g., "192.168.10.1") is valid.
.PARAMETER GWIPAddress
Specifies the IPv4 Gateway IPAddress (e.g., "192.168.10.1").
#>
Param (
[String]$GWIPAddress
)
BEGIN {
$Pattern = "^([1-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])(.([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])){3}$"
}
PROCESS {
$Retval = $GWIPAddress -match $pattern
}
END {
Return $Retval
}
}
Function ConvertTo-IPv4MaskString {
<#
.SYNOPSIS
Converts a number of bits (0-32) to an IPv4 network mask string (e.g., "255.255.255.0").
.DESCRIPTION
Converts a number of bits (0-32) to an IPv4 network mask string (e.g., "255.255.255.0").
.PARAMETER MaskBits
Specifies the number of bits in the mask.
#>
param(
[parameter(Mandatory=$true)]
[ValidateRange(0,32)]
[Int] $MaskBits
)
BEGIN {
}
PROCESS {
$mask = ([Math]::Pow(2, $MaskBits) - 1) * [Math]::Pow(2, (32 - $MaskBits))
$bytes = [BitConverter]::GetBytes([UInt32] $mask)
$Retval = (($bytes.Count - 1)..0 | ForEach-Object { [String] $bytes[$_] }) -join "."
}
END {
Return $Retval
}
}
Function Test-IPv4MaskString {
<#
.SYNOPSIS
Tests whether an IPv4 network mask string (e.g., "255.255.255.0") is valid.
.DESCRIPTION
Tests whether an IPv4 network mask string (e.g., "255.255.255.0") is valid.
.PARAMETER MaskString
Specifies the IPv4 network mask string (e.g., "255.255.255.0").
#>
Param (
[string]$MaskString
)
BEGIN{
}
PROCESS {
$bValidMask = $true
$ArrSections = @()
$ArrSections +=$MaskString.split(".")
# Firstly, make sure there are 4 sections in the subnet mask
if ($ArrSections.count -ne 4) {
$bValidMask =$False
}
# Secondly, make sure it only contains numbers and it's between 0-255
if ($bValidMask) {
foreach ($item in $arrSections) {
if(!($item -match "^d+$")) {
$bValidMask = $False
}
}
}
if ($bValidMask) {
foreach ($item in $arrSections)
{
$item = [int]$item
if ($item -lt 0 -or $item -gt 255) {$bValidMask = $False}
}
}
# Lastly, make sure it is actually a subnet mask when converted into binary format
if ($bValidMask) {
foreach ($item in $arrSections)
{
$binary = [Convert]::ToString($item,2)
if ($binary.length -lt 8)
{
do {
$binary = "0$binary"
} while ($binary.length -lt 8)
}
$strFullBinary = $strFullBinary+$binary
}
if ($strFullBinary.contains("01")) {$bValidMask = $False}
if ($bValidMask)
{
$strFullBinary = $strFullBinary.replace("10", "1.0")
if ((($strFullBinary.split(".")).count -ne 2)) {$bValidMask = $False}
}
}
}
END {
Return $bValidMask
}
}
Function Test-Bitmask {
<#
.SYNOPSIS
Test if bitmask is in the range between 1 and 32
.DESCRIPTION
Validate if Bitmask is in valid range
.PARAMETER Bitmask
Set Bitmask, Must be between 1-32)
.INPUTS
Lenght : Interger
.OUTPUTS
Retval : boolen
.EXAMPLE
Test-Bitmask 24
#>
Param (
[string]$Bitmask
)
BEGIN {
}
PROCESS {
If ($Bitmask -lt 32) {
$Retval = $true
}
Else {
$Retval = $False
}
}
END {
Return $Retval
}
}
Function Resolve-IPv4NetworkSpace {
<#
.SYNOPSIS
Get IPv4 Address Space from Gateway IPAddress
.DESCRIPTION
Get IPv4 Address Space from Gateway IPAddress
.PARAMETER $GWIPAddress
IP Address of Gateway
.INPUTS
GWIPAddress : String
.OUTPUTS
Retval : String
.EXAMPLE
Resolve-IPv4NetworkSpace 192.168.10.1
#>
Param (
[string]$GWIPAddress
)
BEGIN {
}
PROCESS {
# Last IP octect for Switch IPAddress
$LastOctect = '0'
# Get IPAdress Space forNetwork
$Retval = ($GWIPAddress -replace '(d+.d+.d+.)(d+)','$1')+$LastOctect
}
END {
Return $Retval
}
}
# Launch the window
$SyncHash.Window.WindowStartupLocation="CenterScreen"
$SyncHash.Window.Add_Loaded( {
$this.TopMost = $true
})
# Set Focus on first textbox
$SyncHash.TextBox_SwitchName.Focus() | out-null
# Disable Create NetNat button
$SyncHash.Button_CreateNetNat.IsEnabled = $False
# Show the window
$SyncHash.Window.ShowDialog() | out-null