Tests/unit/global.unit.tests.ps1

Describe "[Global Unit]" -Tag Global {
    Context "[Global Unit]-[Test Host: $($env:COMPUTERNAME)]-System Requirements" {
        It "[Global Unit]-[TestHost: ${env:ComputerName}] must be the minimum required major version of Validate-DCB" {
            (Get-Module Validate-DCB -ListAvailable).Version.Major | Should BeGreaterThan '20190904'
        }

        It "[Global Unit]-[TestHost: ${env:ComputerName}] must be the minimum required minimum version of Validate-DCB" {
            (Get-Module Validate-DCB -ListAvailable).Version.Revision | Should BeGreaterThan '60'
        }

        $pesterModule = (Get-Module -Name Pester -ListAvailable -ErrorAction SilentlyContinue)

        ### Verify TestHost has Pester Module
        It "[Global Unit]-[TestHost: ${env:ComputerName}] must have Pester module" {
            $pesterModule | Should Not BeNullOrEmpty
        }

        $NodeOS = Get-CimInstance -ClassName 'Win32_OperatingSystem'

        ### Verify the TestHost is sufficient version
        It "[Global Unit]-[TestHost: ${env:ComputerName}] must be Windows 10, Server 2016, Server 2019, or Server 2022 or HCI OS" {
            $caption =  ($NodeOS.Caption -like '*Windows 10*') -or
                        ($NodeOS.Caption -like '*Windows Server 2016*') -or
                        ($NodeOS.Caption -like '*Windows Server 2019*') -or
                        ($NodeOS.Caption -like '*Windows Server 2022*') -or
                        ($NodeOS.Caption -like '*Azure Stack HCI*')

            $caption | Should be $true
        }

        ### Verify PowerShell Modules are available on the TestHost
        $reqModules  = @('ServerManager','DcbQos', 'NetQos', 'NetAdapter')

        # Add Hyper-V cmdlets to the list if there's at least one vmSwitch defined in the config
        If ($ConfigData.AllNodes.VMSwitch.Count -ge 1) {
            $reqModules += 'Hyper-V'
        }

        $reqModules | ForEach-Object {
            It "[Global Unit]-[TestHost: ${env:ComputerName}] Must have the module [$_] available" {
                $module = Get-Module $_ -ListAvailable -ErrorAction SilentlyContinue
                $module | Should not BeNullOrEmpty
            }
        }

        #Import-Module "$here\helpers\NetworkConfig\NetworkConfig.psd1"
        If ($Deploy) {
            $fnNetworkConfig = Get-ChildItem 'Function:\NetworkConfig'
            It "[Global Unit]-[TestHost: ${env:ComputerName}] must have the NetworkConfig command" {
                $fnNetworkConfig | Should not BeNullOrEmpty
            }

            It "[Global Unit]-[TestHost: ${env:ComputerName}] NetworkConfig command must be a Configuration" {
                $fnNetworkConfig.CommandType | Should be 'Configuration'
            }
        }

        ### Verify PowerShell cmdlets are available on the TestHost
        $reqCmdlets  = @('Get-WindowsFeature','Get-NetQosPolicy', 'Get-NetQosFlowControl',
                         'Get-NetQosTrafficClass', 'Get-NetAdapterQos', 'Get-NetQosDcbxSetting')

        # Add Hyper-V cmdlets to the list if there's at least one vmSwitch defined in the config
        If ($ConfigData.AllNodes.VMSwitch.Count -ge 1) {
            $reqCmdlets += 'Get-VMSwitch'
        }

        $reqCmdlets | ForEach-Object {
            It "[Global Unit]-[TestHost: ${env:ComputerName}] must have the cmdlet [$_] available" {
                $cmd = Get-Command $_ -ErrorAction SilentlyContinue
                $cmd | Should not BeNullOrEmpty
            }
        }
    }

    Context "[Global Unit]-Config File Integrity" {
        ### Verify the config file exists
        $configFileExists = Get-ChildItem -Path $configFile -ErrorAction SilentlyContinue
        It "[Config File] $($configFileExists.Name) must exist" {
            $configFileExists.Exists | Should Be $true
        }

        ### Verify configData contains the AllNodes HashTable
        It "[Config File]-[AllNodes] Config File must contain the AllNodes Hashtable" {
            $configData.AllNodes | Should BeOfType System.Collections.Hashtable
        }

        ### Verify configData contains the NonNodeData HashTable
        It "[Config File]-[NonNodeData] Config File must contain the NonNodeData Hashtable" {
            $configData.NonNodeData | Should BeOfType System.Collections.Hashtable
        }

        $legend = @('AllNodes','NonNodeData', 'Drivers')
        $configData.Keys.GetEnumerator() | ForEach-Object {
            ### Verify that the entries under $configData are in $legend
            It "[Config File]-[Tested key: $_] Should contain only recognized keys" {
                $_ -in $legend | Should be $true
            }
        }

        ### Verify at least one node is specified [NodeName]
        It "[Config File]-[AllNodes.NodeName] Config File must contain at least 1 Node" {
            $configData.AllNodes.NodeName.Count | Should BeGreaterThan 0
        }

        ### Verify nodes are only listed once in the config file [NodeName]
        $reference = $configData.AllNodes.NodeName | Select-Object -Unique -ErrorAction SilentlyContinue

        It "[Config File]-[AllNodes.NodeName] Nodes cannot be specified more than once in the config file" {
            Compare-Object -ReferenceObject $reference -DifferenceObject $ConfigData.AllNodes.NodeName | Should BeNullOrEmpty
        }

        ### Verify Config File includes at least one RDMAEnabledAdapters entry
        It "[Config File]-[AllNodes] Must include at least one RDMAEnabledAdapters or vmswitch.RDMAEnabledAdapters entry" {
            ($configData.AllNodes.RDMAEnabledAdapters + $configData.AllNodes.vmswitch.RDMAEnabledAdapters).Count | Should BeGreaterThan 0
        }

        foreach ($thisNode in $configData.AllNodes) {
            $AdapterEntry = 1

            $legend = @('Role','NodeName','RDMAEnabledAdapters','RDMADisabledAdapters','VMSwitch')
            ($configData.AllNodes.Keys.GetEnumerator() | Select-Object -Unique) | ForEach-Object {
                ### Verify that the entries under $configData.AllNodes are in $legend
                It "[Config File]-[AllNodes]-[$($thisNode.NodeName)]-[Tested key: $_] Should contain only recognized keys" {
                    $_ -in $legend | Should be $true
                }
            }

            foreach ($thisRDMADisabledAdapter in $thisNode.RDMADisabledAdapters) {
                $legend = @('Name')
                ($thisRDMADisabledAdapter.Keys.GetEnumerator() | Select-Object -Unique) | ForEach-Object {
                    ### Verify that the only entries under $configData.AllNodes.RDMADisabledAdapters are in $legend
                    It "[Config File]-[AllNodes.RDMADisabledAdapters]-[RDMADisabledAdapter: $($thisRDMADisabledAdapter.Name)]-[Tested key: $_]-Should contain only recognized keys" {
                        $_ -in $legend | Should be $true
                    }
                }
            }

            foreach ($thisRDMAEnabledAdapter in $thisNode.RDMAEnabledAdapters) {
                $legend = @('Name','VLANID','JumboPacket','EncapOverhead')
                ($thisRDMAEnabledAdapter.Keys.GetEnumerator() | Select-Object -Unique) | ForEach-Object {
                    ### Verify that the entries under $configData.AllNodes.RDMAEnabledAdapters are in $legend
                    It "[Config File]-[AllNodes.RDMAEnabledAdapters]-[RDMAEnabledAdapter: $($thisRDMAEnabledAdapter.Name)]-[Tested key: $_]-Should contain only recognized keys" {
                        $_ -in $legend | Should be $true
                    }
                }

                ### Verify each RDMAEnabledAdapter includes the Name property from Get-NetAdapter
                It "[Config File]-[AllNodes.RDMAEnabledAdapters]-[Node: $($thisNode.NodeName)]-[Entry: $AdapterEntry]-[Noun: NetAdapter] Must include the Name property for each entry" {
                    $thisRDMAEnabledAdapter.Name | Should not BeNullOrEmpty
                }

                ### Verify each RDMAEnabledAdapter includes the VLANID property from Get-NetAdapterAdvancedProperty
                It "[Config File]-[AllNodes.RDMAEnabledAdapters]-[Node: $($thisNode.NodeName)]-[Entry: $($thisRDMAEnabledAdapter.Name)]-[Noun: NetAdapterAdvancedProperty] Must include the VLANID property for each entry" {
                    $thisRDMAEnabledAdapter.VLANID | Should not BeNullOrEmpty
                }

                ### Verify each RDMAEnabledAdapter's VLANID property is not '0'
                It "[Config File]-[AllNodes.RDMAEnabledAdapters]-[Node: $($thisNode.NodeName)]-[Entry: $($thisRDMAEnabledAdapter.Name)]-[Noun: NetAdapterAdvancedProperty] VLANID property should not be non-zero" {
                    $thisRDMAEnabledAdapter.VLANID | Should Not Be '0'
                }

                ### Verify RDMAEnabledAdapter Entry is not included in RDMADisabledAdapter
                It "[Config File]-[AllNodes.RDMAEnabledAdapters]-[Node: $($thisNode.NodeName)]-[Entry: $($thisRDMAEnabledAdapter.Name)]-[Noun: NetAdapterRDMA] Should not be in both RDMAEnabledAdapters and RDMADisabledAdapters" {
                    $thisRDMAEnabledAdapter.Name -in $thisNode.RDMADisabledAdapters.Name | Should Be $false
                }

                ### Verify RDMAEnabledAdapter Entry is not included in vmswitch.RDMADisabledAdapter
                It "[Config File]-[AllNodes.RDMAEnabledAdapters]-[Node: $($thisNode.NodeName)]-[Entry: $($thisRDMAEnabledAdapter.Name)]-[Noun: NetAdapterRDMA] Should not be in both RDMAEnabledAdapters and vmswitch.RDMADisabledAdapters" {
                    $thisRDMAEnabledAdapter.Name -in $thisNode.vmswitch.RDMADisabledAdapters.Name | Should Be $false
                }

                $AdapterEntry ++
            }

            $AdapterEntry = 1
            $VMSwitchEntry = 1

            foreach ($thisVMSwitch in $thisNode.vmSwitch) {
                $RDMADisabledAdapters = $thisVMSwitch.RDMADisabledAdapters
                $legend = @('Name','VMNetworkAdapter')

                foreach ($thisRDMADisabledAdapter in $RDMADisabledAdapters) {
                    $thisRDMADisabledAdapter.Keys.GetEnumerator() | ForEach-Object {
                        ### Verify entries under $configData.AllNodes.VMSwitch.RDMADisabledAdapters are in $legend
                        It "[Config File]-[AllNodes.VMSwitch.RDMADisabledAdapters]-[VMSwitch: $($thisVMSwitch.Name))]-[RDMADisabledAdapter: $($thisRDMADisabledAdapter.($_))]-[Tested key: $_]-Should contain only recognized keys" {
                            $_ -in $legend | Should be $true
                        }
                    }
                }
            }

            foreach ($thisVMSwitch in $thisNode.VMSwitch) {
                $legend = @('Name','EmbeddedTeamingEnabled','IovEnabled','LoadBalancingAlgorithm','RDMAEnabledAdapters','RDMADisabledAdapters')
                $thisVMSwitch.Keys.GetEnumerator() | ForEach-Object {
                    ### Verify that the entries under $configData.AllNodes.VMSwitch are in $legend
                    It "[Config File]-[AllNodes.VMSwitch.RDMAEnabledAdapters]-[Node: $($thisNode.NodeName)]-[Entry: $($thisVMSwitch.Name))]-[Tested key: $_]-Should contain only recognized keys" {
                        $_ -in $legend | Should be $true
                    }
                }

                ### Verify VMSwitch entry contains the Name property
                It "[Config File]-[AllNodes.VMSwitch]-[Node: $($thisNode.NodeName)]-[Entry: $VMSwitchEntry)]-[Noun: VMSwitch] Must include the Name Property" {
                    $thisVMSwitch.Name | Should not BeNullOrEmpty
                }

                ### Verify VMSwitch entry contains the EmbeddedTeamingEnabled property
                It "[Config File]-[AllNodes.VMSwitch]-[Node: $($thisNode.NodeName)]-[Entry: $($thisVMSwitch.Name))]-[Noun: VMSwitch] Must include the EmbeddedTeamingEnabled Property" {
                    $thisVMSwitch.EmbeddedTeamingEnabled | Should not BeNullOrEmpty
                }

                ### Verify VMSwitch EmbeddedTeamingEnabled property is a boolean
                It "[Config File]-[AllNodes.VMSwitch]-[Node: $($thisNode.NodeName)]-[Entry: $($thisVMSwitch.Name))]-[Noun: VMSwitch] EmbeddedTeamingEnabled Property must be a boolean" {
                    $thisVMSwitch.EmbeddedTeamingEnabled | Should BeOfType System.Boolean
                }

                If ($thisVMSwitch.ContainsKey('IovEnabled')) {
                    ### Verify VMSwitch IovEnabled property is a boolean
                    It "[Config File]-[AllNodes.VMSwitch]-[Node: $($thisNode.NodeName)]-[Entry: $($thisVMSwitch.Name))]-[Noun: VMSwitch] The IovEnabled property must be boolean" {
                        $thisVMSwitch.IovEnabled | Should BeOfType System.Boolean
                    }
                }

                If ($thisVMSwitch.ContainsKey('LoadBalancingAlgorithm')) {
                    ### Verify VMSwitch Load Balancing Algorithm is either Dynamic (2016 Default) or HyperVPort (2019 Default)
                    It "[Config File]-[AllNodes.VMSwitch]-[Node: $($thisNode.NodeName)]-[Entry: $($thisVMSwitch.Name))]-[Noun: VMSwitch] The LoadBalancingAlgorithm property must be either HyperVPort or Dynamic " {
                        ($thisVMSwitch.LoadBalancingAlgorithm -eq 'Dynamic' -or $thisVMSwitch.LoadBalancingAlgorithm -eq 'HyperVPort') | Should be $true
                    }
                }

                $reference = $thisNode.VMSwitch.Name | Select-Object -Unique -ErrorAction SilentlyContinue

                ### Verify VMSwitch.Name entries are unique
                It "[Config File]-[AllNodes.VMSwitch] VMSwitch.Name cannot be specified more than once in the config file" {
                    Compare-Object -ReferenceObject $reference -DifferenceObject $thisNode.VMSwitch.Name | Should BeNullOrEmpty
                }

                foreach ($thisRDMAEnabledAdapter in $thisVMSwitch.RDMAEnabledAdapters) {
                    $legend = @('Name','VMNetworkAdapter','VLANID','JumboPacket','EncapOverhead')
                    $thisRDMAEnabledAdapter.Keys.GetEnumerator() | ForEach-Object {
                        ### Verify that the only entries under $configData.AllNodes.VMSwitch.RDMAEnabledAdapters are in $legend
                        It "[Config File]-[AllNodes.VMSwitch.RDMAEnabledAdapters]-[Node: $($thisNode.NodeName)]-[Entry: $($thisVMSwitch.Name))]-[RDMAEnabledAdapter: $($thisRDMAEnabledAdapter.Name)]-[Tested Key: $_]-Should contain only recognized keys" {
                            $_ -in $legend | Should be $true
                        }
                    }

                    ### Verify each VMSwitch.RDMAEnabledAdapter includes the Name property from Get-NetAdapter
                    It "[Config File]-[AllNodes.VMSwitch.RDMAEnabledAdapters]-[Node: $($thisNode.NodeName)]-[Entry: $AdapterEntry]-[Noun: NetAdapter] Must include the Name property for each entry" {
                        $thisRDMAEnabledAdapter.Name | Should not BeNullOrEmpty
                    }

                    ### Verify each VMSwitch.RDMAEnabledAdapter includes the Name property from Get-VMNetworkAdapter -ManagementOS
                    It "[Config File]-[AllNodes.VMSwitch.RDMAEnabledAdapters]-[Node: $($thisNode.NodeName)]-[Entry: $($thisRDMAEnabledAdapter.Name)]-[Noun: VMNetworkAdapter] Must include the VMNetworkAdapter property for each entry" {
                        $thisRDMAEnabledAdapter.VMNetworkAdapter | Should not BeNullOrEmpty
                    }

                    ### Verify each VMSwitch.RDMAEnabledAdapter includes the VLANID property from Get-NetAdapterAdvancedProperty
                    It "[Config File]-[AllNodes.VMSwitch.RDMAEnabledAdapters]-[Node: $($thisNode.NodeName)]-[Entry: $($thisRDMAEnabledAdapter.Name)]-[Noun: NetAdapterAdvancedProperty] Must include the VLANID property for each entry" {
                        $thisRDMAEnabledAdapter.VLANID | Should not BeNullOrEmpty
                    }

                    ### Verify each VMSwitch.RDMAEnabledAdapter's VLANID property is not 0
                    It "[Config File]-[AllNodes.VMSwitch.RDMAEnabledAdapters]-[Node: $($thisNode.NodeName)]-[Entry: $($thisRDMAEnabledAdapter.Name)]-[Noun: NetAdapterAdvancedProperty] VLANID property should not be non-zero" {
                        $thisRDMAEnabledAdapter.VLANID | Should Not Be '0'
                    }

                    ### Verify each VMSwitch.RDMAEnabledAdapter entry is not included in RDMADisabledAdapter
                    It "[Config File]-[AllNodes.VMSwitch.RDMAEnabledAdapters]-[Node: $($thisNode.NodeName)]-[Entry: $($thisRDMAEnabledAdapter.Name)]-[Noun: NetAdapterRDMA] Should not be in both VMSwitch.RDMAEnabledAdapters and RDMADisabledAdapters" {
                        $thisRDMAEnabledAdapter.Name -in $thisNode.RDMADisabledAdapters.Name | Should Be $false
                    }

                    ### Verify VMSwitch.RDMAEnabledAdapter Entry is not included in VMSwitch.RDMADisabledAdapter
                    It "[Config File]-[AllNodes.VMSwitch.RDMAEnabledAdapters]-[Node: $($thisNode.NodeName)]-[Entry: $($thisRDMAEnabledAdapter.Name)]-[Noun: NetAdapterRDMA] Should not be in both VMSwitch.RDMAEnabledAdapters and VMSwitch.RDMADisabledAdapters" {
                        $thisRDMAEnabledAdapter.Name -in $thisNode.VMSwitch.RDMADisabledAdapters.Name | Should Be $false
                    }

                    $AdapterEntry ++
                }

                $VMSwitchEntry ++
            }
        }

        $legend = @('NetQos', 'AzureAutomation')
        $configData.NonNodeData.Keys.GetEnumerator() | ForEach-Object {
            ### Verify that the only entries under $configData.NonNodeData are in $legend
            It "[Config File]-[NonNodeData]-[Tested key: $_] Should contain only recognized keys" {
                $_ -in $legend | Should be $true
            }
        }

        ### Verify NetQos is included in the config file
        It "[Config File]-[NonNodeData.NetQos] Config File must contain the NetQos section" {
            $ConfigData.NonNodeData.NetQos | Should not BeNullOrEmpty
        }

        ### Verify at least 3 policies exist in the Qos Policies (Default and one for SMB)
        It "[Config File]-[NonNodeData.NetQos]-[Noun: NetQosPolicy] NetQos must contain at least 3 policies" {
            $ConfigData.NonNodeData.NetQos.Count | Should BeGreaterThan 2
        }

        ### Verify the default policy is specified in the config file
        It "[Config File]-[NonNodeData.NetQos]-[Noun: NetQosPolicy] NetQos must specify a 'Default' policy" {
            $ConfigData.NonNodeData.NetQos.Name -contains 'Default' | Should be $true
        }

        ### Verify the Cluster policy is specified in the config file
        It "[Config File]-[NonNodeData.NetQos]-[Noun: NetQosPolicy] NetQos must specify a Cluster policy" {
            $ConfigData.NonNodeData.NetQos.Template -contains 'Cluster' | Should be $true
        }

        # Note: Templates only specify TCP settings and do not apply to RDMA
        # For RDMA, please use NetDirectPortMatchCondition

        ### Verify At least one policy must specify the NetDirectPortMatchCondition
        It "[Config File]-[NonNodeData.NetQos]-[Noun: NetQosPolicy] must specify the 'NetDirectPortMatchCondition' property for exactly 1 policy" {
            $ConfigData.NonNodeData.NetQos.Keys -contains 'NetDirectPortMatchCondition' | Should be $true
        }

        ### Verify Cluster Bandwidth Percentage is equal to 1%
        It "[Config File]-[NonNodeData.NetQos]-[Noun: NetQosTrafficClass] Cluster BandwidthPercentage must be 2% or less" {
            ($ConfigData.NonNodeData.NetQos.GetEnumerator().Where{ $_.Template -eq 'Cluster' }).BandwidthPercentage | Should BeLessThan 3
        }

        ### Verify BandwidthPercentage totals 100
        It "[Config File]-[NonNodeData.NetQos]-[Noun: NetQosTrafficClass] BandwidthPercentage must total 100%" {
            ($ConfigData.NonNodeData.NetQos.BandwidthPercentage | Measure-Object -Sum).Sum | Should Be 100
        }

        $policyEntry = 1

        foreach ($thisPolicy in $ConfigData.NonNodeData.NetQos) {
            $legend = @('Name', 'NetDirectPortMatchCondition', 'Template', 'PriorityValue8021Action', 'BandwidthPercentage', 'Algorithm')
            $thisPolicy.Keys.GetEnumerator() | ForEach-Object {
                ### Verify that the only entries under $configData.NonNodeData.NetQos are in $legend
                It "[Config File]-[NonNodeData]-[Tested key: $_] Should contain only recognized keys" {
                    $_ -in $legend | Should be $true
                }
            }
            ### Verify Name property is specified for each policy
            It "[Config File]-[NonNodeData.NetQos]-[Policy Entry: $policyEntry]-[Noun: NetQosPolicy] Must specify the 'Name' property" {
                $thisPolicy.Name | Should not BeNullOrEmpty
            }

            ### Verify either NetDirectPortMatchCondition or Template are specified
            It "[Config File]-[NonNodeData.NetQos]-[Policy: $($thisPolicy.Name)]-[Noun: NetQosPolicy] Must specify either 'Template' or 'NetDirectPortMatchCondition' property" {
                ($thisPolicy.NetDirectPortMatchCondition -or $thisPolicy.Template) | Should Be $true
            }

            ### Verify PriorityValue8021Action is specified
            It "[Config File]-[NonNodeData.NetQos]-[Policy: $($thisPolicy.Name)]-[Noun: NetQosPolicy] Must specify the 'PriorityValue8021Action' property" {
                $thisPolicy.PriorityValue8021Action | Should not BeNullOrEmpty
            }

            ### Verify BandwidthPercentage is specified
            It "[Config File]-[NonNodeData.NetQos]-[Policy: $($thisPolicy.Name)]-[Noun: NetQosTrafficClass] Must specify the 'BandwidthPercentage' property" {
                $thisPolicy.BandwidthPercentage | Should not BeNullOrEmpty
            }

            ### Verify Algorithm is specified
            It "[Config File]-[NonNodeData.NetQos]-[Policy: $($thisPolicy.Name)]-[Noun: NetQosTrafficClass] Must specify the 'Algorithm' property" {
                $thisPolicy.Algorithm | Should not BeNullOrEmpty
            }

            $policyEntry ++
        }
    }

    $ConfigData.AllNodes | ForEach-Object {
        $nodeName = $_.NodeName

        Context "[Global Unit]-[SUT: $nodeName]-Connectivity Tests" {
            ### Verify Basic Network Connectivity to each Node
            It "[Global Unit]-[SUT: $nodeName] must respond to available over the network" {
                Test-NetConnection -ComputerName $nodeName -InformationLevel Quiet | Should Be $true
            }

            ### Verify each node responds to WinRM
            It "[Global Unit]-[SUT: $nodeName] must respond to WinRM requests" {
                Test-NetConnection -ComputerName $nodeName -CommonTCPPort WINRM -InformationLevel Quiet | Should Be $true
            }
        }

        Context "[Global Unit]-[SUT: $nodeName]-System Requirements" {
            $NodeOS = Get-CimInstance -CimSession $nodeName -ClassName 'Win32_OperatingSystem'

            ### Verify the SUTs are Server SKU, 2016 or Higher
            It "[Global Unit]-[SUT: $nodeName] must be Server 2016, Server 2019, or Server 2022" {
                $caption =  ($NodeOS.Caption -like '*Windows Server 2016*') -or
                            ($NodeOS.Caption -like '*Windows Server 2019*') -or
                            ($NodeOS.Caption -like '*Windows Server 2022*') -or
                            ($NodeOS.Caption -like '*Azure Stack HCI*')

                $caption | Should be $true
            }

            $reqModules  = @('DcbQos', 'NetQos', 'NetAdapter','ServerManager')
            $reqFeatures = @('Data-Center-Bridging')

            # Add Hyper-V to the list if there's at least one vmSwitch defined in the config
            If ($ConfigData.AllNodes.VMSwitch.Count -ge 1) {
                $reqFeatures += 'Hyper-V'
                $reqModules  += 'Hyper-V'
            }

            if ($nodeName -match $env:COMPUTERNAME)
            {
                $actModules = Get-Module -Name $reqModules -ListAvailable -ErrorAction SilentlyContinue
                $actFeatureState = Get-WindowsFeature -Name $reqFeatures -ErrorAction SilentlyContinue
                $actModuleCmdletNames = $actModules.ExportedCommands.Values.Name
            }
            else
            {
                $actModules, $actFeatureState = Invoke-Command -ComputerName $nodeName -ScriptBlock {
                    $modules      = Get-Module         -Name $using:reqModules -ListAvailable -ErrorAction SilentlyContinue
                    $featureState = Get-WindowsFeature -Name $using:reqFeatures -ErrorAction SilentlyContinue                    
                    return $Modules, $featureState
                }
                $actModuleCmdletNames = $actModules.ExportedCommands.Values
            }

            ### Verify that the required features exist on the SUT
            $reqFeatures | ForEach-Object {
                It "[Global Unit]-[SUT: $nodeName] should have the Windows Feature [$_] installed" {
                    ($actFeatureState | Where-Object Name -eq $_).InstallState | Should be 'Installed'
                }
            }

            ### Verify that each required module existed on the SUT
            $reqModules | ForEach-Object {
                It "[Global Unit]-[SUT: $nodeName] should have the module [$_] installed" {
                    ($actModules | Where-Object Name -eq $_) | Sort-Object Version -Descending | Select-Object -First 1 | Should be $true
                }
            }

            ### Verify the following cmdlets are available on each SUT
            $reqCmdlets  = @('Get-WindowsFeature','Get-NetQosPolicy', 'Get-NetQosFlowControl',
                            'Get-NetQosTrafficClass', 'Get-NetAdapterQos', 'Get-NetQosDcbxSetting')

            # Add Hyper-V cmdlets to the list if there's at least one vmSwitch defined in the config
            If ($ConfigData.AllNodes.VMSwitch.Count -ge 1) {
                $reqCmdlets += 'Get-VMSwitch'
            }

            $reqCmdlets | ForEach-Object {
                It "[Global Unit]-[SUT: $nodeName] should have the cmdlet [$_] available" {
                    $actModuleCmdletNames -contains $_ | Should be $true
                }
            }

            ### Verify none of the nodes are actually virtual machines
            It "[Global Unit]-[SUT: $nodeName] should not be a virtual machine" {
                (Get-CimInstance -ComputerName $nodeName -ClassName Win32_ComputerSystem) | Should Not Be 'Virtual Machine'
            }
        }
    }

    If ($deploy) {
        Context "Deployment" {
            $AzureRMContext = Get-AzureRmContext -ErrorAction SilentlyContinue
            It '[Global Unit]-[Noun: AzureRMContext] Azure Context should be logged in using Connect-AzureRMAccount' {
                $AzureRMContext | Should Not BeNullOrEmpty
            }

            It "[Global Unit]-[Noun: AzureRMContext] Azure Context specifies an account. Current Account is [$($AzureRMContext.Account.ID)]" {
                $AzureRMContext.Account | Should Not BeNullOrEmpty
            }

            It "[Global Unit]-[Noun: AzureRMContext] Azure Context specifies a subscription. Current subscription [$($AzureRMContext.Subscription.Name)]" {
                $AzureRMContext.Subscription.Name | Should Not BeNullOrEmpty
            }

            $AzureRMResourceGroup = Get-AzureRmResourceGroup -Name $configData.NonNodeData.AzureAutomation.ResourceGroupName -ErrorAction SilentlyContinue
            It "[Global Unit]-[Noun: AzureRmResourceGroup] Azure Resource Group [$($configData.NonNodeData.AzureAutomation.ResourceGroupName)] should be available in the account" {
                $AzureRMResourceGroup | Should Not BeNullOrEmpty
            }

            # Sub-expression required for cmdlet to work properly
            $AzureRMAutomationAccount = Get-AzureRmAutomationAccount -Name $configData.NonNodeData.AzureAutomation.AutomationAccountName -ResourceGroupName $($configData.NonNodeData.AzureAutomation.ResourceGroupName) -ErrorAction SilentlyContinue

            It "[Global Unit]-[Noun: AzureRmAutomationAccount] Azure Automation Account [$($configData.NonNodeData.AzureAutomation.AutomationAccountName)] should be found in the Resource Group" {
                $AzureRMAutomationAccount | Should Not BeNullOrEmpty
            }
        }
    }
}

Describe "[Global Unit]" -Tag Launch_Deploy {
    Context "[Global Unit]-[Test Host: $($env:COMPUTERNAME)]-Launch and Deploy prerequisites" {
        $reqModules = Import-PowerShellDataFile -Path "$here\helpers\NetworkConfig\NetworkConfig.psd1"

        ($reqModules).RequiredModules.GetEnumerator() | ForEach-Object {
            Remove-Variable module -ErrorAction SilentlyContinue

            $module = Get-Module $_.ModuleName -ListAvailable -ErrorAction SilentlyContinue | Select -First 1

            It "[Global Unit]-[TestHost: ${env:ComputerName}] Must have the module [$($_.ModuleName)] available" {
                $module.Name | Should Not BeNullOrEmpty
            }

            If ($_.ContainsKey('ModuleVersion')) {
                It "[Global Unit]-[TestHost: ${env:ComputerName}] Must be at least version $($_.ModuleVersion)" {
                    $module.version -ge $_.ModuleVersion | Should be $true
                }
            }
        }

        ### Verify PowerShell cmdlets are available on the TestHost
        $reqCmdlets  = @('Get-ClusterNode')

        $reqCmdlets | ForEach-Object {
            It "[Global Unit]-[TestHost: ${env:ComputerName}] must have the cmdlet [$_] available" {
                $cmd = Get-Command $_ -ErrorAction SilentlyContinue
                $cmd | Should not BeNullOrEmpty
            }
        }

        $reqDSCResources = @('DCBNetAdapterQos', 'DCBNetQosDcbxSetting', 'DCBNetQosFlowControl',
                             'DCBNetQosPolicy', 'DCBNetQosTrafficClass', 'VMNetworkAdapterIsolation',
                             'VMNetworkAdapterSettings', 'VMNetworkAdapterTeamMapping', 'WindowsFeature',
                             'NetAdapterAdvancedProperty', 'NetAdapterRDMA', 'xVMSwitch',
                             'xVMNetworkAdapter', 'NetAdapterRsc' )

        $reqDSCResources | ForEach-Object {
            It "[Global Unit]-[TestHost: ${env:ComputerName}] must have the DSC Resource [$_] available" {
                $cmd = Get-DSCResource $_ -ErrorAction SilentlyContinue
                $cmd | Should not BeNullOrEmpty
            }
        }

        $module = Import-Module -Name  "$here\helpers\NetworkConfig\NetworkConfig.psd1" -PassThru -Force -ErrorAction SilentlyContinue

        It "[Global Unit]-[TestHost: ${env:ComputerName}] must import the NetworkConfig module successfully" {
            $module | Should Not BeNullOrEmpty
        }

        $module = Import-Module -Name  "$here\helpers\UI\vDCBUI.psm1" -PassThru -Force -ErrorAction SilentlyContinue

        It "[Global Unit]-[TestHost: ${env:ComputerName}] must import the vDCBUI module successfully" {
            $module | Should Not BeNullOrEmpty
        }
    }
}