lib/public/switch.ps1
<#
.SYNOPSIS Gets an array of switches from a Lab. .DESCRIPTION Takes a provided Lab and returns the array of LabSwitch objects required for this Lab. This list is usually passed to Initialize-LabSwitch to configure the switches required for this lab. .PARAMETER Lab Contains the Lab object that was loaded by the Get-Lab object. .PARAMETER Name An optional array of Switch names. Only Switches matching names in this list will be pulled into the returned in the array. .EXAMPLE $Lab = Get-Lab -ConfigPath c:\mylab\config.xml $Switches = Get-LabSwitch -Lab $Lab Loads a Lab and pulls the array of switches from it. .OUTPUTS Returns an array of LabSwitch objects. #> function Get-LabSwitch { [OutputType([LabSwitch[]])] [CmdLetBinding()] param ( [Parameter( Position = 1, Mandatory = $true)] [ValidateNotNullOrEmpty()] $Lab, [Parameter( Position = 2)] [ValidateNotNullOrEmpty()] [String[]] $Name ) [System.String] $LabId = $Lab.labbuilderconfig.settings.labid [LabSwitch[]] $Switches = @() $ConfigSwitches = $Lab.labbuilderconfig.Switches.Switch foreach ($ConfigSwitch in $ConfigSwitches) { # It can't be switch because if the name attrib/node is missing the name property on the # XML object defaults to the name of the parent. So we can't easily tell if no name was # specified or if they actually specified 'switch' as the name. $SwitchName = $ConfigSwitch.Name if ($Name -and ($SwitchName -notin $Name)) { # A names list was passed but this swtich wasn't included continue } # if if ($SwitchName -eq 'switch') { $exceptionParameters = @{ errorId = 'SwitchNameIsEmptyError' errorCategory = 'InvalidArgument' errorMessage = $($LocalizedData.SwitchNameIsEmptyError) } New-LabException @exceptionParameters } # Convert the switch type string to a LabSwitchType $SwitchType = [LabSwitchType]::$($ConfigSwitch.Type) # If the SwitchType string doesn't match any enum value it will be # set to null. if (-not $SwitchType) { $exceptionParameters = @{ errorId = 'UnknownSwitchTypeError' errorCategory = 'InvalidArgument' errorMessage = $($LocalizedData.UnknownSwitchTypeError ` -f $ConfigSwitch.Type, $SwitchName) } New-LabException @exceptionParameters } # if # if a LabId is set for the lab, prepend it to the Switch name as long as it isn't # an external switch. if ($LabId -and ($SwitchType -ne [LabSwitchType]::External)) { $SwitchName = "$LabId$SwitchName" } # if # Assemble the list of Mangement OS Adapters if any are specified for this switch # Only Intenal and External switches are allowed Management OS adapters. if ($ConfigSwitch.Adapters) { [LabSwitchAdapter[]] $ConfigAdapters = @() foreach ($Adapter in $ConfigSwitch.Adapters.Adapter) { $AdapterName = $Adapter.Name # if a LabId is set for the lab, prepend it to the adapter name. # But only if it is not an External switch. if ($LabId -and ($SwitchType -ne [LabSwitchType]::External)) { $AdapterName = "$LabId$AdapterName" } $ConfigAdapter = [LabSwitchAdapter]::New($AdapterName) $ConfigAdapter.MACAddress = $Adapter.MacAddress $ConfigAdapters += @( $ConfigAdapter ) } # foreach if (($ConfigAdapters.Count -gt 0) ` -and ($SwitchType -notin [LabSwitchType]::External, [LabSwitchType]::Internal)) { $exceptionParameters = @{ errorId = 'AdapterSpecifiedError' errorCategory = 'InvalidArgument' errorMessage = $($LocalizedData.AdapterSpecifiedError ` -f $SwitchType, $SwitchName) } New-LabException @exceptionParameters } # if } else { $ConfigAdapters = $null } # if # Create the new Switch object [LabSwitch] $NewSwitch = [LabSwitch]::New($SwitchName, $SwitchType) $NewSwitch.VLAN = $ConfigSwitch.VLan $NewSwitch.BindingAdapterName = $ConfigSwitch.BindingAdapterName $NewSwitch.BindingAdapterMac = $ConfigSwitch.BindingAdapterMac $NewSwitch.BindingAdapterMac = $ConfigSwitch.BindingAdapterMac $NewSwitch.NatSubnet = $ConfigSwitch.NatSubnet $NewSwitch.NatGatewayAddress = $ConfigSwitch.NatGatewayAddress $NewSwitch.Adapters = $ConfigAdapters $Switches += @( $NewSwitch ) } # foreach return $Switches } # Get-LabSwitch <# .SYNOPSIS Creates Hyper-V Virtual Switches from a provided array of LabSwitch objects. .DESCRIPTION Takes an array of LabSwitch objectsthat were pulled from a Lab object by calling Get-LabSwitch and ensures that they Hyper-V Virtual Switches on the system are configured to match. .PARAMETER Lab Contains Lab object that was loaded by the Get-Lab object. .PARAMETER Name An optional array of Switch names. Only Switches matching names in this list will be initialized. .PARAMETER Switches The array of LabSwitch objects pulled from the Lab using Get-LabSwitch. If not provided it will attempt to pull the array from the Lab object provided. .EXAMPLE $Lab = Get-Lab -ConfigPath c:\mylab\config.xml $Switches = Get-LabSwitch -Lab $Lab Initialize-LabSwitch -Lab $Lab -Switches $Switches Initializes the Hyper-V switches in the configured in the Lab c:\mylab\config.xml .EXAMPLE $Lab = Get-Lab -ConfigPath c:\mylab\config.xml Initialize-LabSwitch -Lab $Lab Initializes the Hyper-V switches in the configured in the Lab c:\mylab\config.xml .OUTPUTS None. #> function Initialize-LabSwitch { [CmdLetBinding()] param ( [Parameter( Position = 1, Mandatory = $true)] [ValidateNotNullOrEmpty()] $Lab, [Parameter( Position = 2)] [ValidateNotNullOrEmpty()] [String[]] $Name, [Parameter( Position = 3)] [LabSwitch[]] $Switches ) # if switches was not passed, pull it. if (-not $PSBoundParameters.ContainsKey('switches')) { [LabSwitch[]] $Switches = Get-LabSwitch ` @PSBoundParameters } # Create Hyper-V Switches foreach ($VMSwitch in $Switches) { if ($Name -and ($VMSwitch.name -notin $Name)) { # A names list was passed but this swtich wasn't included continue } # if if ((Get-VMSwitch | Where-Object -Property Name -eq $($VMSwitch.Name)).Count -eq 0) { [System.String] $SwitchName = $VMSwitch.Name if (-not $SwitchName) { $exceptionParameters = @{ errorId = 'SwitchNameIsEmptyError' errorCategory = 'InvalidArgument' errorMessage = $($LocalizedData.SwitchNameIsEmptyError) } New-LabException @exceptionParameters } [LabSwitchType] $SwitchType = $VMSwitch.Type Write-LabMessage -Message $($LocalizedData.CreatingVirtualSwitchMessage ` -f $SwitchType, $SwitchName) Switch ($SwitchType) { 'External' { # Determine which Physical Adapter to bind this switch to if ($VMSwitch.BindingAdapterMac) { $BindingAdapter = Get-NetAdapter | Where-Object { ($_.MacAddress -replace '-', '') -eq $VMSwitch.BindingAdapterMac } $ErrorDetail = "with a MAC address '$($VMSwitch.BindingAdapterMac)' " } elseif ($VMSwitch.BindingAdapterName) { $BindingAdapter = Get-NetAdapter ` -Name $VMSwitch.BindingAdapterName ` -ErrorAction SilentlyContinue $ErrorDetail = "with a name '$($VMSwitch.BindingAdapterName)' " } else { $BindingAdapter = Get-NetAdapter | ` Where-Object { ($_.Status -eq 'Up') ` -and (-not $_.Virtual) ` } | Select-Object -First 1 $ErrorDetail = '' } # if # Check that a Binding Adapter was found if (-not $BindingAdapter) { $exceptionParameters = @{ errorId = 'BindingAdapterNotFoundError' errorCategory = 'InvalidArgument' errorMessage = $($LocalizedData.BindingAdapterNotFoundError ` -f $SwitchName, $ErrorDetail) } New-LabException @exceptionParameters } # if # Check this adapter is not already bound to a switch $VMSwitchNames = (Get-VMSwitch | Where-Object { $_.SwitchType -eq 'External' }).Name $MacAddress = @() foreach ($VmSwitchName in $VmSwitchNames) { $MacAddress += (Get-VMNetworkAdapter ` -ManagementOS ` -SwitchName $VmSwitchName ` -Name $VmSwitchName ` -ErrorAction SilentlyContinue).MacAddress } # foreach $UsedAdapters = @((Get-NetAdapter | Where-Object { ($_.MacAddress -replace '-', '') -in $MacAddress }).Name) if ($BindingAdapter.Name -in $UsedAdapters) { $exceptionParameters = @{ errorId = 'BindingAdapterUsedError' errorCategory = 'InvalidArgument' errorMessage = $($LocalizedData.BindingAdapterUsedError ` -f $SwitchName, $BindingAdapter.Name) } New-LabException @exceptionParameters } # if # Create the swtich $null = New-VMSwitch ` -Name $SwitchName ` -NetAdapterName $BindingAdapter.Name break } # 'External' 'Private' { $null = New-VMSwitch ` -Name $SwitchName ` -SwitchType Private Break } # 'Private' 'Internal' { $null = New-VMSwitch ` -Name $SwitchName ` -SwitchType Internal Break } # 'Internal' 'NAT' { if ($Script:CurrentBuild -lt 14295) { $exceptionParameters = @{ errorId = 'NatSwitchNotSupportedError' errorCategory = 'InvalidArgument' errorMessage = $($LocalizedData.NatSwitchNotSupportedError -f $SwitchName) } New-LabException @exceptionParameters } $NatSubnet = $VMSwitch.NatSubnet # Check Nat Subnet is set if (-not $NatSubnet) { $exceptionParameters = @{ errorId = 'NatSubnetEmptyError' errorCategory = 'InvalidArgument' errorMessage = $($LocalizedData.NatSubnetEmptyError ` -f $SwitchName) } New-LabException @exceptionParameters } # if # Ensure Nat Subnet looks valid if ($NatSubnet -notmatch '[0-9]+.[0-9]+.[0-9]+.[0-9]+/[0-9]+') { $exceptionParameters = @{ errorId = 'NatSubnetInvalidError' errorCategory = 'InvalidArgument' errorMessage = $($LocalizedData.NatSubnetInvalidError ` -f $SwitchName, $NatSubnet) } New-LabException @exceptionParameters } # if $NatSubnetComponents = ($NatSubnet -split '/') $NatSubnetAddress = $NatSubnetComponents[0] # Validate the Nat Subnet Address if (-not ([System.Net.Ipaddress]::TryParse($NatSubnetAddress, [ref]0))) { $exceptionParameters = @{ errorId = 'NatSubnetAddressInvalidError' errorCategory = 'InvalidArgument' errorMessage = $($LocalizedData.NatSubnetAddressInvalidError ` -f $SwitchName, $NatSubnetAddress) } New-LabException @exceptionParameters } # if # Validate the Nat Subnet Prefix Length [int] $NatSubnetPrefixLength = $NatSubnetComponents[1] if (($NatSubnetPrefixLength -lt 1) -or ($NatSubnetPrefixLength -gt 31)) { $exceptionParameters = @{ errorId = 'NatSubnetPrefixLengthInvalidError' errorCategory = 'InvalidArgument' errorMessage = $($LocalizedData.NatSubnetPrefixLengthInvalidError ` -f $SwitchName, $NatSubnetPrefixLength) } New-LabException @exceptionParameters } # if $NatGatewayAddress = $VMSwitch.NatGatewayAddress # Create the Internal Switch $null = New-VMSwitch ` -Name $SwitchName ` -SwitchType Internal ` -ErrorAction Stop # Set the IP Address on the default adapter connected to the NAT switch $MacAddress = (Get-VMNetworkAdapter ` -ManagementOS ` -SwitchName $SwitchName ` -Name $SwitchName ` -ErrorAction Stop).MacAddress if ([System.String]::IsNullOrEmpty($MacAddress)) { $exceptionParameters = @{ errorId = 'NatSwitchDefaultAdapterMacEmptyError' errorCategory = 'InvalidArgument' errorMessage = $($LocalizedData.NatSwitchDefaultAdapterMacEmptyError ` -f $SwitchName) } New-LabException @exceptionParameters } # if $Adapter = Get-NetAdapter | Where-Object { ($_.MacAddress -replace '-', '') -eq $MacAddress } if (-not $Adapter) { $exceptionParameters = @{ errorId = 'NatSwitchDefaultAdapterNotFoundError' errorCategory = 'InvalidArgument' errorMessage = $($LocalizedData.NatSwitchDefaultAdapterNotFoundError ` -f $SwitchName) } New-LabException @exceptionParameters } $null = $Adapter | New-NetIPAddress ` -IPAddress $NatGatewayAddress ` -PrefixLength $NatSubnetPrefixLength ` -ErrorAction Stop # Does the NAT already exist? $NetNat = Get-NetNat ` -Name $SwitchName ` -ErrorAction SilentlyContinue if ($NetNat) { # If the NAT already exists, remove it so it can be recreated $null = $NetNat | Remove-NetNat -Confirm:$False } # Create the new NAT $null = New-NetNat ` -Name $SwitchName ` -InternalIPInterfaceAddressPrefix $NatSubnet ` -ErrorAction Stop Break } # 'NAT' Default { $exceptionParameters = @{ errorId = 'UnknownSwitchTypeError' errorCategory = 'InvalidArgument' errorMessage = $($LocalizedData.UnknownSwitchTypeError ` -f $SwitchType, $SwitchName) } New-LabException @exceptionParameters } } # switch if ($SwitchType -ne 'Private') { # Configure the VLan on the default Management Adapter $Splat = @{ Name = $SwitchName SwitchName = $SwitchName } if ($VMSwitch.VLan) { $Splat += @{ VlanId = $VMSwitch.Vlan } } # if UpdateSwitchManagementAdapter @Splat # Add any management OS adapters to the switch if ($VMSwitch.Adapters) { foreach ($Adapter in $VMSwitch.Adapters) { $Splat = @{ Name = $Adapter.Name SwitchName = $SwitchName } if ($Adapter.MacAddress) { $Splat += @{ StaticMacAddress = $Adapter.MacAddress } } # if if ($VMSwitch.VLan) { $Splat += @{ VlanId = $VMSwitch.Vlan } } # if UpdateSwitchManagementAdapter @Splat } # foreach } # if } # if } # if } # foreach } # Initialize-LabSwitch <# .SYNOPSIS Removes all Hyper-V Virtual Switches provided. .DESCRIPTION This cmdlet is used to remove any Hyper-V Virtual Switches that were created by the Initialize-LabSwitch cmdlet. .PARAMETER Lab Contains the Lab object that was loaded by the Get-Lab object. .PARAMETER Name An optional array of Switch names. Only Switches matching names in this list will be removed. .PARAMETER Switches The array of LabSwitch objects pulled from the Lab using Get-LabSwitch. If not provided it will attempt to pull the array from the Lab object. .EXAMPLE $Lab = Get-Lab -ConfigPath c:\mylab\config.xml $Switches = Get-LabSwitch -Lab $Lab Remove-LabSwitch -Lab $Lab -Switches $Switches Removes any Hyper-V switches in the configured in the Lab c:\mylab\config.xml .EXAMPLE $Lab = Get-Lab -ConfigPath c:\mylab\config.xml Remove-LabSwitch -Lab $Lab Removes any Hyper-V switches in the configured in the Lab c:\mylab\config.xml .OUTPUTS None. #> function Remove-LabSwitch { [CmdLetBinding()] param ( [Parameter( Position = 1, Mandatory = $true)] [ValidateNotNullOrEmpty()] $Lab, [Parameter( Position = 2)] [ValidateNotNullOrEmpty()] [String[]] $Name, [Parameter( Position = 3)] [LabSwitch[]] $Switches ) # if switches were not passed so pull them if (-not $PSBoundParameters.ContainsKey('switches')) { [LabSwitch[]] $Switches = Get-LabSwitch ` @PSBoundParameters } # Delete Hyper-V Switches foreach ($VMSwitch in $Switches) { if ($Name -and ($VMSwitch.name -notin $Name)) { # A names list was passed but this swtich wasn't included continue } # if if ((Get-VMSwitch | Where-Object -Property Name -eq $VMSwitch.Name).Count -ne 0) { [System.String] $SwitchName = $VMSwitch.Name if (-not $SwitchName) { $exceptionParameters = @{ errorId = 'SwitchNameIsEmptyError' errorCategory = 'InvalidArgument' errorMessage = $($LocalizedData.SwitchNameIsEmptyError) } New-LabException @exceptionParameters } [LabSwitchType] $SwitchType = $VMSwitch.Type Write-LabMessage -Message $($LocalizedData.DeleteingVirtualSwitchMessage ` -f $SwitchType, $SwitchName) Switch ($SwitchType) { 'External' { if ($VMSwitch.Adapters) { $VMSwitch.Adapters.foreach( { $null = Remove-VMNetworkAdapter ` -ManagementOS ` -Name $_.Name } ) } # if Remove-VMSwitch ` -Name $SwitchName Break } # 'External' 'Private' { Remove-VMSwitch ` -Name $SwitchName Break } # 'Private' 'Internal' { Remove-VMSwitch ` -Name $SwitchName if ($VMSwitch.Adapters) { $VMSwitch.Adapters.foreach( { $null = Remove-VMNetworkAdapter ` -ManagementOS ` -Name $_.Name } ) } # if Break } # 'Internal' 'NAT' { Remove-NetNat ` -Name $SwitchName Remove-VMSwitch ` -Name $SwitchName Break } # 'Internal' Default { $exceptionParameters = @{ errorId = 'UnknownSwitchTypeError' errorCategory = 'InvalidArgument' errorMessage = $($LocalizedData.UnknownSwitchTypeError ` -f $SwitchType, $SwitchName) } New-LabException @exceptionParameters } } # Switch } # if } # foreach } # Remove-LabSwitch |