DSCResources/cVMSwitch/cVMSwitch.psm1

#region localizeddata
if (Test-Path "${PSScriptRoot}\${PSUICulture}")
{
    Import-LocalizedData -BindingVariable localizedData -filename cVMSwitch.psd1 `
                         -BaseDirectory "${PSScriptRoot}\${PSUICulture}"
} 
else
{
    #fallback to en-US
    Import-LocalizedData -BindingVariable localizedData -filename cVMSwitch.psd1 `
                         -BaseDirectory "${PSScriptRoot}\en-US"
}
#endregion

function Get-TargetResource
{
    [CmdletBinding()]
    [OutputType([System.Collections.Hashtable])]
    param
    (
        [parameter(Mandatory)]
        [String]$Name,

        [parameter(Mandatory)]
        [ValidateSet("External","Internal","Private")]
        [String]$Type
    )

    if(!(Get-Module -ListAvailable -Name Hyper-V))
    {
        Throw $localizedData.HyperVModuleNotFound
    }
        
    $configuration = @{
        Name = $Name
        Type = $Type
    }

    $switch = Get-VMSwitch -Name $Name -SwitchType $Type -ErrorAction SilentlyContinue
    if ($switch)
    {
        Write-Verbose -Message $localizedData.FoundSwitch
        if ($switch.SwitchType -eq 'External')
        {
            Write-Verbose -Message $localizedData.FoundExternalSwitch

            #SET specific properties for External switch type
            if ($switch.EmbeddedTeamingEnabled)
            {
                Write-Verbose -Message $localizedData.FoundSetTeam
                $switchTeam = Get-VMSwitchTeam -Name $Name

                $netAdapterName = $(
                    $switchTeam.NetAdapterInterfaceDescription | 
                        Foreach-Object { 
                            (Get-NetAdapter -InterfaceDescription $_).Name
                        }
                )               
                $configuration.Add('TeamingMode',$switchTeam.TeamingMode)
                $configuration.Add('LoadBalancingAlgorithm',$switchTeam.LoadBalancingAlgorithm)
            }
            else
            {
                $netAdapterName = $( 
                    if($switch.NetAdapterInterfaceDescription)
                    {
                        (Get-NetAdapter -InterfaceDescription $switch.NetAdapterInterfaceDescription).Name
                    }
                )
            }
        }
        else
        {
            Write-Verbose -Message ($localizedData.FoundIntORPvtSwitch -f $switch.SwitchType)
        }
        
        $configuration.Add('NetAdapterName', $netAdapterName)
        $configuration.Add('NetAdapterInterfaceDescription',$switch.NetAdapterInterfaceDescriptions)
        $configuration.Add('EmbeddedTeamingEnabled',$switch.EmbeddedTeamingEnabled)
        $configuration.Add('AllowManagementOS',$switch.AllowManagementOS)
        $configuration.Add('Id',$switch.Id)
        $configuration.Add('EnableIoV',$switch.IovEnabled)
        $configuration.Add('EnablePacketDirect',$switch.PacketDirectEnabled)
        $configuration.Add('MinimumBandwidthMode',$switch.BandwidthReservationMode)
        $configuration.Add('Ensure','Present')
    }
    else
    {
        Write-Verbose -Message $localizedData.NoSwitchFound
        $configuration.Add('Ensure','Absent')
    }

    return $configuration
}

function Set-TargetResource
{
     [CmdletBinding()]
     param
     (
         [parameter(Mandatory)]
         [String] $Name,

         [parameter(Mandatory)]
         [ValidateSet('External','Internal','Private')]
         [String] $Type,

        [Parameter()]
        [ValidateNotNullOrEmpty()]
        [String[]] $NetAdapterName,

        [Parameter()]
        [Boolean] $AllowManagementOS,

        [Parameter()]
        [Boolean] $EnableIov,

        [Parameter()]
        [ValidateSet('None', 'Default', 'Weight', 'Absolute')]
        [String] $MinimumBandwidthMode='Absolute',
        
        [parameter()]
        [ValidateSet('SwitchIndependent')]
        [String] $TeamingMode,

        [parameter()]
        [ValidateSet('Dynamic','HyperVPort')]
        [String] $LoadBalancingAlgorithm,

        [Parameter()]
        [Boolean]$EnablePacketDirect,

        [ValidateSet("Present","Absent")]
        [String] $Ensure = "Present"
    )
    
    # Check if Hyper-V module is present for Hyper-V cmdlets
    if(!(Get-Module -ListAvailable -Name Hyper-V))
    {
        Throw $localizedData.HyperVModuleNotFound
    }

    if ((($Type -eq 'Internal') -or ($Type -eq 'Private')) -and $AllowManagementOS)
    {
        throw $localizedData.InternalPrivateWithAllowManagementOS
    }

    if ($Type -eq 'External' -and !($NetAdapterName))
    {
        throw $localizedData.NetAdapterNameForExternal
    }

    if ($Type -ne 'External' -and $NetAdapterName)
    {
        throw $localizedData.NoNetAdapterInternalPrivate
    }

    if (($TeamingMode -or $LoadBalancingAlgorithm) -and ($Type -ne 'External'))
    {
        throw $localizedData.NoSETForInternalPrivate
    }

    if ($EnableIov -and $EnablePacketDirect)
    {
        throw $localizedData.IOVPDTogether
    }

    if ($MinimumBandwidthMode -and ($EnableIov -and ($NetAdapterName.Count -gt 1)))
    {
        throw $localizedData.IOVMBwithSET
    }

    if ($MinimumBandwidthMode -and ($EnablePacketDirect -and ($NetAdapterName.Count -gt 1)))
    {
        throw $localizedData.PDMBwithSET
    }

    if($Ensure -eq 'Present')
    {
        $switch = (Get-VMSwitch -Name $Name -SwitchType $Type -ErrorAction SilentlyContinue)

        # If switch is present and it is external type, that means it doesn't have right properties (TEST code ensures that)
        if($switch)
        {
            Write-Verbose -Message $localizedData.FoundSwitch
            if ($switch.SwitchType -eq 'External')
            {
                Write-Verbose -Message $localizedData.FoundExternalSwitch
                
                #Check if there are multiple network adapters specified; it should be a SET Team
                if ($NetAdapterName.Count -gt 1)
                {
                    #We need a SET Team
                    Write-Verbose -Message $localizedData.NeedASET
                    if (-not $switch.EmbeddedTeamingEnabled)
                    {
                        #We dont have a SET team; delete and re-create the team
                        Write-Verbose -Message $localizedData.ReCreateSET

                        $switch | Remove-VMSwitch -Force
                        $arguments = @{
                            Name = $Name
                            NetAdapterName = $NetAdapterName
                            MinimumBandwidthMode = $MinimumBandwidthMode
                        }
                        
                        if ($PSBoundParameters.ContainsKey('AllowManagementOS'))
                        {
                            $arguments['AllowManagementOS']=$AllowManagementOS
                        }
                        
                        if ($PSBoundParameters.ContainsKey('TeamingMode'))
                        {
                            $arguments['TeamingMode']=$TeamingMode
                        }
                        
                        if ($PSBoundParameters.ContainsKey('LoadBalancingAlgorithm'))
                        {
                            $arguments['LoadBalancingAlgorithm']=$LoadBalancingAlgorithm
                        }
                        
                        if ($PSBoundParameters.ContainsKey("EnableIov"))
                        {
                            $arguments['EnableIov']=$EnableIov
                        }
                        
                        if ($PSBoundParameters.ContainsKey("EnablePacketDirect"))
                        {
                            $arguments['EnablePacketDirect']=$EnablePacketDirect
                        }
                        
                        Write-Verbose -Message $localizedData.CreateSwitch
                        $null = New-VMSwitch @arguments
                    }
                    else
                    {
                        #We have a SET Team; check network adapters and other properties
                        Write-Verbose -Message $localizedData.SETFoundCheckNetAdapter

                        $switchTeam = Get-VMSwitchTeam -VMSwitch $switch
                        $existngNetAdapters = $switchTeam.NetAdapterInterfaceDescription | 
                                ForEach-Object {
                                    (Get-NetAdapter -InterfaceDescription $_).Name
                                }
                        $switchTeamMembers = Compare-Object -ReferenceObject $NetAdapterName -DifferenceObject $existngNetAdapters
                    
                        $setTeamArguments = @{
                            VMSwitch = $switch
                        }

                        if ($null -ne $switchTeamMembers)
                        {
                            #We have a difference in the compared objects
                            Write-Verbose -Message $localizedData.SETMembersDontMatch

                            $setTeamArguments['NetAdapterName'] = $NetAdapterName
                            $updateTeam = $true
                        }

                        #check other propeties of the SET Team as well
                        if ($PSBoundParameters.ContainsKey('TeamingMode'))
                        {
                            if ($switchTeam.TeamingMode -ne $TeamingMode)
                            {
                                $setTeamArguments['TeamingMode']=$TeamingMode
                                $updateTeam = $true
                            }
                        }

                        if ($PSBoundParameters.ContainsKey('LoadBalancingAlgorithm'))
                        {
                            if ($switchTeam.LoadBalancingAlgorithm -ne $LoadBalancingAlgorithm)
                            {
                                $setTeamArguments['LoadBalancingAlgorithm']=$LoadBalancingAlgorithm
                                $updateTeam = $true
                            }
                        }

                        if ($updateTeam)
                        {
                            Write-Verbose -Message $localizedData.UpdateSETTeam
                            $null = Set-VMSwitchTeam @setTeamArguments
                        }

                        #Finally, check if if we need set AllowManagementOS
                        if($PSBoundParameters.ContainsKey("AllowManagementOS"))
                        {
                            Write-Verbose -Message "Checking if Switch $Name has AllowManagementOS set correctly..."
                            if(($switch.AllowManagementOS -ne $AllowManagementOS))
                            {
                                Write-Verbose -Message $localizedData.UpdateSwitch
                                $null = Set-VMSwitch -VMSwitch $switch -AllowManagementOS $AllowManagementOS
                            }
                        }
                    } 
                }
                else
                {
                    #We don't need a SET Team
                    if ($switch.EmbeddedTeamingEnabled)
                    {
                        #We have SET team; need to delete and re-create a normal switch
                        Write-Verbose -Message $localizedData.NeedANormalSwitch
                        Write-Verbose -Message $localizedData.RemovingSwitch
                        $switch | Remove-VMSwitch -Force

                        $switchArguments = @{
                            Name = $Name
                            NetAdapterName = $NetAdapterName
                            MinimumBandwidthMode = $MinimumBandwidthMode
                        }

                        if ($PSBoundParameters.ContainsKey('AllowManagementOS'))
                        {
                            $switchArguments['AllowManagementOS']=$AllowManagementOS
                        }
                    
                        if ($PSBoundParameters.ContainsKey("EnableIov"))
                        {
                            $switchArguments['EnableIov']=$EnableIov
                        }
                        if ($PSBoundParameters.ContainsKey("EnablePacketDirect"))
                        {
                            $switchArguments['EnablePacketDirect']=$EnablePacketDirect
                        }

                        Write-Verbose -Message $localizedData.CreateSwitch
                        $null = New-VMSwitch @switchArguments
                    }
                    else
                    {
                        #We have a normal switch; Check other properties
                        $switchUpdateArguments = @{
                            VMSwitch = $switch                            
                        }

                        if((Get-NetAdapter -Name $NetAdapterName).InterfaceDescription -ne $switch.NetAdapterInterfaceDescription)
                        {
                            #Network Adapter is not matching; we can set this without deleting the switch
                            $switchUpdateArguments['NetAdapterName'] = $NetAdapterName
                            $updateSwitch = $true
                        }
                    
                        if($PSBoundParameters.ContainsKey("AllowManagementOS"))
                        {
                            Write-Verbose -Message "Checking if Switch $Name has AllowManagementOS set correctly..."
                            if(($switch.AllowManagementOS -ne $AllowManagementOS))
                            {
                                $switchUpdateArguments['AllowManagementOS'] = $AllowManagementOS
                                $updateSwitch = $true
                            }
                        }
                    
                        if ($updateSwitch)
                        {
                            Write-Verbose -Message $localizedData.UpdateSwitch
                            $null = Set-VMSwitch @switchUpdateArguments
                        }
                    }
                }
            }
            else
            {
                #We have an internal or private switch; we cannot update any properties
                Write-Verbose -Message $localizedData.WeShouldNeverReachHere
            }
        }
        else
        {
            # If the switch is not present, create one
            $parameters = @{}
            $parameters["Name"] = $Name
            if($NetAdapterName)
            {
                $parameters["NetAdapterName"] = $NetAdapterName
                $parameters["MinimumBandwidthMode"] = $MinimumBandwidthMode
                if($PSBoundParameters.ContainsKey("AllowManagementOS"))
                {
                    $parameters["AllowManagementOS"] = $AllowManagementOS
                }
            }
            else
            { 
                $parameters["SwitchType"] = $Type
            }
            
            Write-Verbose -Message $localizedData.CreateSwitch
            $null = New-VMSwitch @parameters
        }
    }
    # Ensure is set to "Absent", remove the switch
    else
    {
        Write-Verbose -Message $localizedData.RemovingSwitch
        Get-VMSwitch $Name -ErrorAction SilentlyContinue | Remove-VMSwitch -Force
    }
}

function Test-TargetResource
{
    [CmdletBinding()]
    [OutputType([System.Boolean])]
    param
    (
        [parameter(Mandatory)]
        [String] $Name,

        [parameter(Mandatory)]
        [ValidateSet('External','Internal','Private')]
        [String] $Type,

        [Parameter()]
        [ValidateNotNullOrEmpty()]
        [String[]] $NetAdapterName,

        [Parameter()]
        [Boolean] $AllowManagementOS,

        [Parameter()]
        [Boolean] $EnableIov,

        [Parameter()]
        [ValidateSet('None', 'Default', 'Weight', 'Absolute')]
        [String] $MinimumBandwidthMode='Absolute',
        
        [parameter()]
        [ValidateSet('SwitchIndependent')]
        [String] $TeamingMode,

        [parameter()]
        [ValidateSet('Dynamic','HyperVPort')]
        [String] $LoadBalancingAlgorithm,

        [Parameter()]
        [Boolean]$EnablePacketDirect,

        [ValidateSet("Present","Absent")]
        [String] $Ensure = "Present"
    )

    # Check if Hyper-V module is present for Hyper-V cmdlets
    if(!(Get-Module -ListAvailable -Name Hyper-V))
    {
        Throw $localizedData.HyperVModuleNotFound
    }

    if ((($Type -eq 'Internal') -or ($Type -eq 'Private')) -and $AllowManagementOS)
    {
        throw $localizedData.InternalPrivateWithAllowManagementOS
    }

    if ($Type -eq 'External' -and !($NetAdapterName))
    {
        throw $localizedData.NetAdapterNameForExternal
    }

    if ($Type -ne 'External' -and $NetAdapterName)
    {
        throw $localizedData.NoNetAdapterInternalPrivate
    }

    if (($TeamingMode -or $LoadBalancingAlgorithm) -and ($Type -ne 'External'))
    {
        throw $localizedData.NoSETForInternalPrivate
    }

    if ($EnableIov -and $EnablePacketDirect)
    {
        throw $localizedData.IOVPDTogether
    }

    if ($MinimumBandwidthMode -and ($EnableIov -and ($NetAdapterName.Count -gt 1)))
    {
        throw $localizedData.IOVMBwithSET
    }

    if ($MinimumBandwidthMode -and ($EnablePacketDirect -and ($NetAdapterName.Count -gt 1)))
    {
        throw $localizedData.PDMBwithSET
    }

    try
    {
        $switch = Get-VMSwitch -Name $Name -SwitchType $Type -ErrorAction Stop

        if($switch)
        {
            Write-Verbose -Message $localizedData.FoundSwitch
            if ($Ensure -eq 'Present')
            {
                if ($NetAdapterName.Count -gt 1)
                {
                    Write-Verbose -Message $localizedData.NeedASET
                    #We need a SET team
                    if (-not $switch.EmbeddedTeamingEnabled)
                    {
                        Write-Verbose -Message $localizedData.ReCreateSET
                        return $false
                    }
                    else
                    {
                        #We have a SET team; need to compare the properties
                        $switchTeam = Get-VMSwitchTeam -Name $Name

                        if ($Type -eq 'External')
                        {
                            #Compare network adapters in the SET
                            Write-Verbose -Message $localizedData.SETFoundCheckNetAdapter
                            $existngNetAdapters = $switchTeam.NetAdapterInterfaceDescription | 
                                ForEach-Object { 
                                    (Get-NetAdapter -InterfaceDescription $_).Name 
                                }
                            $switchTeamMembers = Compare-Object -ReferenceObject $NetAdapterName `
                                                 -DifferenceObject $existngNetAdapters

                            if ($null -ne $switchTeamMembers)
                            {
                                #We have a difference in the compared objects
                                Write-Verbose -Message $localizedData.SETMembersDontMatch
                                return $false
                            }
                        }

                        if ($switchTeam.LoadBalancingAlgorithm -ne $LoadBalancingAlgorithm)
                        {
                            Write-Verbose -Message $localizedData.LBDifferent
                            return $false
                        }

                        if ($switchTeam.TeamingMode -ne $TeamingMode)
                        {
                            Write-Verbose -Message $localizedData.TeamingDifferent
                            return $false
                        }
                    }
                }
                else
                {
                    #We need a normal VM switch
                    if ($switch.EmbeddedTeamingEnabled)
                    {
                        Write-Verbose -Message $localizedData.NeedANormalSwitch
                        return $false
                    }

                    if ($Type -eq 'External')
                    {
                        if((Get-NetAdapter -Name $NetAdapterName -ErrorAction SilentlyContinue).InterfaceDescription -ne $switch.NetAdapterInterfaceDescription)
                        {
                            Write-Verbose -Message $localizedData.NetAdapterDifferent
                            return $false
                        }
                    }
                }

                #Check for common properties
                if($PSBoundParameters.ContainsKey("AllowManagementOS") -and $Type -eq 'External')
                {
                    if(($switch.AllowManagementOS -ne $AllowManagementOS))
                    {
                        Write-Verbose -Message $localizedData.AllowMgmtOSDifferent
                        return $false
                    }
                }

                if ($EnablePacketDirect)
                {
                    if (-not $switch.EnablePacketDirect) {
                        Write-Warning -Message $localizedData.EPDCannotChange
                    }                    
                }

                if ($EnableIov)
                {
                    if (-not $switch.EnableIov) {
                        Write-Warning -Message $localizedData.IOVCannotChange
                    } 
                }

                if ($MinimumBandwidthMode -ne $switch.BandwidthReservationMode)
                {
                    Write-Warning -Message $localizedData.MBCannotChange
                }
                
                #If we have reached this far, the switch exists with necessary configuration
                Write-Verbose -Message $localizedData.SwitchExistsNoAction
                return $true
            }
            else
            {
                Write-Verbose -Message $localizedData.SwitchExistsItShouldnot
                return $false       
            }
        }
        else
        {
            if ($Ensure -eq 'Present')
            {
                Write-Verbose -Message $localizedData.SwitchShouldExist
                return $false
            }
            else
            {
                Write-Verbose -Message $localizedData.SwitchDoesNotExistNoAction
                return $true
            }
        }
    }

    # If no switch was present
    catch [System.Management.Automation.ActionPreferenceStopException]
    {
        Write-Verbose -Message $localizedData.NoSwitchFound
        return ($Ensure -eq 'Absent')
    }
}

Export-ModuleMember -Function *-TargetResource