Private/TestHyperVExternalvSwitch.ps1

function TestHyperVExternalvSwitch {
    [CmdletBinding(DefaultParameterSetName='ExternalNetworkVM')]
    Param(
        [Parameter(Mandatory=$False)]
        [string]$DownloadDirectory = "$HOME\Downloads",

        [Parameter(Mandatory=$False)]
        [switch]$AllowRestarts,

        [Parameter(Mandatory=$False)]
        [switch]$SkipHyperVInstallCheck,

        [Parameter(Mandatory=$False)]
        [switch]$SkipPreDownloadCheck
    )

    #region >> Variable/Parameter Transforms and PreRun Prep

    Push-Location

    if (!$SkipHyperVInstallCheck) {
        try {
            $HyperVFeaturesInstallResults = InstallHyperVFeatures -ParentFunction $MyInvocation.MyCommand.Name
        }
        catch {
            Write-Error $_
            Write-Error "The InstallHyperVFeatures function (as executed by the $($MyInvocation.MyCommand.Name) function) failed! Halting!"
            $global:FunctionResult = "1"
            Pop-Location
            return
        }
        try {
            $InstallContainersFeatureDismResult = InstallFeatureDism -Feature Containers -ParentFunction $MyInvocation.MyCommand.Name
        }
        catch {
            Write-Error $_
            Write-Error "The InstallFeatureDism function (as executed by the $($MyInvocation.MyCommand.Name) function) failed! Halting!"
            $global:FunctionResult = "1"
            Pop-Location
            return
        }

        if ($HyperVFeaturesInstallResults.InstallResults.Count -gt 0) {
            if (!$AllowRestarts) {
                Write-Warning "You must restart $env:ComputerName before the TestHyperVExternalvSwitch function can proceed! Halting!"
                # IMPORTANT NOTE: The below Write-Output "RestartNeeded" is necessary
                Write-Output "RestartNeeded"
                $global:FunctionResult = "1"
                Pop-Location
                return
            }
            else {
                Restart-Computer -Confirm:$False -Force
            }
        }
    }
    else {
        if (![bool]$(Get-Module -ListAvailable -Name Hyper-V) -and ![bool]$(Get-Module -Name Hyper-V)) {
            Write-Error "Hyper-V does not appear to be installed on $env:ComputerName! Try the function again without the -SkipHyperVInstallCheck switch. Halting!"
            $global:FunctionResult = "1"
            Pop-Location
            return
        }
    }

    # Set some other variables that we will need
    <#
    $NextHop = $(Get-NetRoute -AddressFamily IPv4 | Where-Object {$_.NextHop -ne "0.0.0.0"} | Sort-Object RouteMetric)[0].NextHop
    $PrimaryIP = $(Find-NetRoute -RemoteIPAddress $NextHop | Where-Object {$($_ | Get-Member).Name -contains "IPAddress"}).IPAddress
    $NicInfo = Get-NetIPAddress -IPAddress $PrimaryIP
    $NicAdapter = Get-NetAdapter -InterfaceAlias $NicInfo.InterfaceAlias
    #>

    $PrimaryIfIndex = $(Get-CimInstance Win32_IP4RouteTable | Where-Object {
        $_.Destination -eq '0.0.0.0' -and $_.Mask -eq '0.0.0.0'
    } | Sort-Object Metric1)[0].InterfaceIndex
    $NicInfo = Get-CimInstance Win32_NetworkAdapterConfiguration | Where-Object {$_.InterfaceIndex -eq $PrimaryIfIndex}
    $PrimaryIP = $NicInfo.IPAddress | Where-Object {TestIsValidIPAddress -IPAddress $_}
    $NextHop = $NicInfo.DefaultIPGateway[0]

    $HostNameBIOSInfo = Get-CimInstance Win32_BIOS
    $IntegrationServicesRegistryPath = "HKLM:\SOFTWARE\Microsoft\Virtual Machine\Guest\Parameters"
    $HostNameIntegrationServicesPresent = Test-Path $IntegrationServicesRegistryPath

    if ($HostNameBIOSInfo.SMBIOSBIOSVersion -match "Hyper-V|VirtualBox|VMWare|Xen" -or
    $HostNameBIOSInfo.Manufacturer -match "Hyper-V|VirtualBox|VMWare|Xen" -or
    $HostNameBIOSInfo.Name -match "Hyper-V|VirtualBox|VMWare|Xen" -or
    $HostNameBIOSInfo.SerialNumber -match "Hyper-V|VirtualBox|VMWare|Xen" -or
    $HostNameBIOSInfo.Version -match "Hyper-V|VirtualBox|VMWare|Xen|VRTUAL" -or
    $HostNameIntegrationServicesPresent) {
        $IsVirtual = $True
    }
    else {
        $IsVirtual = $False
    }

    # If there are any pre-existing running VMs using an External vSwitch, assume that External vSwitch works
    [System.Collections.ArrayList][Array]$ExternalvSwitches = Get-VMSwitch -SwitchType External
    if ($ExternalvSwitches.Count -gt 0) {
        $RunningVMs = Get-VM | Where-Object {$_.State -eq "Running"}

        if ($RunningVMs.Count -gt 0) {
            [System.Collections.ArrayList]$FoundRunningVMUsingExternalvSwitch = @()
            foreach ($VMObject in $RunningVMs) {
                if ($ExternalvSwitches.Name -contains $VMObject.NetworkAdapters.SwitchName) {
                    $null = $FoundRunningVMUsingExternalvSwitch.Add($VMObject)
                }
            }
        }
    }

    # If we're on BareMetal or if there are already Hyper-V VMs that are 'Running' that use an External vSwitch,
    # then we can assume External vSwitch works, and we're done here...
    if ($IsVirtual -eq $False -or $FoundRunningVMUsingExternalvSwitch.Count -gt 0) {
        [pscustomobject]@{
            ExternalvSwitchWorks                = $True
            CanReachInternet                    = $True
            CanReachRouter                      = $True
            VirtualizationExtensionsExposed     = $True
            MacAddressSpoofingEnabled           = $True
        }
        return
    }

    # If we reached this point in the function, we have established that the machine that the function is being run on
    # is Virtual

    if (!$NicInfo.DHCPEnabled) {
        Write-Error "The Test-HyperVExternalvSwitch function failed because the External Network (i.e. the Host network) does not appear to have DHCP already available! Try the New-DHCPServer function! Halting!"
        $global:FunctionResult = "1"
        return
    }

    if (!$DownloadDirectory) {
        $DownloadDirectory = "$HOME\Downloads"
    }

    # Write the Universal Unsecure Vagrant Private Key to filesystem
    # From: https://github.com/hashicorp/vagrant/blob/master/keys
    $VagrantPrivKey = @"
-----BEGIN RSA PRIVATE KEY-----
MIIEogIBAAKCAQEA6NF8iallvQVp22WDkTkyrtvp9eWW6A8YVr+kz4TjGYe7gHzI
w+niNltGEFHzD8+v1I2YJ6oXevct1YeS0o9HZyN1Q9qgCgzUFtdOKLv6IedplqoP
kcmF0aYet2PkEDo3MlTBckFXPITAMzF8dJSIFo9D8HfdOV0IAdx4O7PtixWKn5y2
hMNG0zQPyUecp4pzC6kivAIhyfHilFR61RGL+GPXQ2MWZWFYbAGjyiYJnAmCP3NO
Td0jMZEnDkbUvxhMmBYSdETk1rRgm+R4LOzFUGaHqHDLKLX+FIPKcF96hrucXzcW
yLbIbEgE98OHlnVYCzRdK8jlqm8tehUc9c9WhQIBIwKCAQEA4iqWPJXtzZA68mKd
ELs4jJsdyky+ewdZeNds5tjcnHU5zUYE25K+ffJED9qUWICcLZDc81TGWjHyAqD1
Bw7XpgUwFgeUJwUlzQurAv+/ySnxiwuaGJfhFM1CaQHzfXphgVml+fZUvnJUTvzf
TK2Lg6EdbUE9TarUlBf/xPfuEhMSlIE5keb/Zz3/LUlRg8yDqz5w+QWVJ4utnKnK
iqwZN0mwpwU7YSyJhlT4YV1F3n4YjLswM5wJs2oqm0jssQu/BT0tyEXNDYBLEF4A
sClaWuSJ2kjq7KhrrYXzagqhnSei9ODYFShJu8UWVec3Ihb5ZXlzO6vdNQ1J9Xsf
4m+2ywKBgQD6qFxx/Rv9CNN96l/4rb14HKirC2o/orApiHmHDsURs5rUKDx0f9iP
cXN7S1uePXuJRK/5hsubaOCx3Owd2u9gD6Oq0CsMkE4CUSiJcYrMANtx54cGH7Rk
EjFZxK8xAv1ldELEyxrFqkbE4BKd8QOt414qjvTGyAK+OLD3M2QdCQKBgQDtx8pN
CAxR7yhHbIWT1AH66+XWN8bXq7l3RO/ukeaci98JfkbkxURZhtxV/HHuvUhnPLdX
3TwygPBYZFNo4pzVEhzWoTtnEtrFueKxyc3+LjZpuo+mBlQ6ORtfgkr9gBVphXZG
YEzkCD3lVdl8L4cw9BVpKrJCs1c5taGjDgdInQKBgHm/fVvv96bJxc9x1tffXAcj
3OVdUN0UgXNCSaf/3A/phbeBQe9xS+3mpc4r6qvx+iy69mNBeNZ0xOitIjpjBo2+
dBEjSBwLk5q5tJqHmy/jKMJL4n9ROlx93XS+njxgibTvU6Fp9w+NOFD/HvxB3Tcz
6+jJF85D5BNAG3DBMKBjAoGBAOAxZvgsKN+JuENXsST7F89Tck2iTcQIT8g5rwWC
P9Vt74yboe2kDT531w8+egz7nAmRBKNM751U/95P9t88EDacDI/Z2OwnuFQHCPDF
llYOUI+SpLJ6/vURRbHSnnn8a/XG+nzedGH5JGqEJNQsz+xT2axM0/W/CRknmGaJ
kda/AoGANWrLCz708y7VYgAtW2Uf1DPOIYMdvo6fxIB5i9ZfISgcJ/bbCUkFrhoH
+vq/5CIWxCPp0f85R4qxxQ5ihxJ0YDQT9Jpx4TMss4PSavPaBH3RXow5Ohe+bYoQ
NE5OgEXk2wVfZczCZpigBKbKZHNYcelXtTt/nP3rsCuGcM4h53s=
-----END RSA PRIVATE KEY-----
"@


    if (!$(Test-Path "$HOME\.ssh")) {
        $null = New-Item -ItemType Directory -Path "$HOME\.ssh"
    }
    $VagrantInsecurePrivKeyPath = "$HOME\.ssh\insecure_vagrant_private_key.pem"
    if (!$(Test-Path $VagrantInsecurePrivKeyPath)) {
        $VagrantPrivKey | Out-File $VagrantInsecurePrivKeyPath -Encoding ASCII

        if ($PSVersionTable.PSEdition -eq "Core") {
            $FixVPermsAsString = ${Function:FixVagrantPrivateKeyPerms}.Ast.Extent.Text
            Invoke-WinCommand -ComputerName localhost -ScriptBlock {
                Invoke-Expression $args[0]
                FixVagrantPrivateKeyPerms -PathToPrivateKey $args[1]
            } -ArgumentList $FixVPermsAsString,$VagrantInsecurePrivKeyPath
        }
        else {
            FixVagrantPrivateKeyPerms -PathToPrivateKey $VagrantInsecurePrivKeyPath
        }
    }

    <#
    if ([Environment]::OSVersion.Version -lt [version]"10.0.17063") {
        if (![bool]$(Get-Command curl -ErrorAction SilentlyContinue) -or
        $(Get-Command curl -ErrorAction SilentlyContinue).CommandType -eq "Alias"
        ) {
            $InstallCurlResults = Install-Program -ProgramName curl -CommandName curl -UseChocolateyCmdLine
        }
    }
    $CurlCmd = $(Get-Command curl -CommandType Application -All).Source
    if ($CurlCmd.Count -gt 1) {
        $CurlCmd = $(Get-Command curl -CommandType Application -All | Where-Object {$_.Source -match "x64"}).Source
        if (!$CurlCmd) {
            $CurlCmd = $(Get-Command curl -CommandType Application -All).Source[-1]
        }
    }
    #>


    if (![bool]$(Get-Command ssh -ErrorAction SilentlyContinue)) {
        if (![bool]$(Get-Module -ListAvailable ProgramManagement)) {Install-Module ProgramManagement}
        if (![bool]$(Get-Module ProgramManagement)) {Import-Module ProgramManagement}
        $InstallOpenSSHResult = Install-Program -ProgramName openssh -CommandName ssh.exe -ExpectedInstallLocation "C:\Program Files\OpenSSH-Win64"

        if (![bool]$(Get-Service ssh-agent -ErrorAction SilentlyContinue)) {
            if (Test-Path "C:\Program Files\OpenSSH-Win64\install-sshd.ps1") {
                & "C:\Program Files\OpenSSH-Win64\install-sshd.ps1"
            }
            else {
                Write-Warning "Unable to find 'C:\Program Files\OpenSSH-Win64\install-sshd.ps1'! The services 'ssh-agent' and 'sshd' will NOT be installed."
            }
        }
    }

    #endregion >> Variable/Parameter Transforms and PreRun Prep


    #region >> Main Body

    $DeployVMSplatParams = @{
        VagrantBox              = "centos/7"
        VagrantProvider         = "hyperv"
        VMName                  = "CentOS7vSwitchTest"
        VMDestinationDirectory  = $DownloadDirectory
        Memory                  = 1024
        CPUs                    = 1
        ErrorAction             = "SilentlyContinue"
        ErrorVariable           = "DVMErr"
        SkipHyperVInstallCheck  = $True
    }

    try {
        $DeployVMResult = Deploy-HyperVVagrantBoxManually @DeployVMSplatParams
        if (!$DeployVMResult -or
        [bool]$($($DVMErr | Out-String) -match "one of the Hyper-V components is not running") -or
        [bool]$($($DVMErr | Out-String) -match "The operation has timed out")
        ) {
            throw "The Deploy-HyperVVagrantBoxManually function failed! Halting!"
        }
        $VMDeployed = $True
    }
    catch {
        if ([bool]$($($DVMErr | Out-String) -match "one of the Hyper-V components is not running")) {
            if ($IsVirtual) {
                $VirtualizationExtensionsExposed = $False
            }
            else {
                Write-Error "One or more Hyper-V components is not installed! Please check the Hyper-V features that are currenly enabled! Halting!"
                $global:FunctionResult = "1"
                Pop-Location
                return
            }
        }
        elseif ([bool]$($($DVMErr | Out-String) -match "The operation has timed out")) {
            Write-Warning "https://vagrantcloud.com appears to be throttling traffic. Sleeping for 5 minutes before trying again..."
            Start-Sleep -Seconds 300

            try {
                $DeployVMResult = Deploy-HyperVVagrantBoxManually @DeployVMSplatParams
                if (!$DeployVMResult -or
                [bool]$($($DVMErr | Out-String) -match "one of the Hyper-V components is not running") -or
                [bool]$($($DVMErr | Out-String) -match "The operation has timed out")
                ) {
                    throw "The Deploy-HyperVVagrantBoxManually function failed! Halting!"
                }
                $VMDeployed = $True
            }
            catch {
                if ([bool]$($($DVMErr | Out-String) -match "one of the Hyper-V components is not running")) {
                    if ($IsVirtual) {
                        $VirtualizationExtensionsExposed = $False
                    }
                    else {
                        Write-Error "One or more Hyper-V components is not installed! Please check the Hyper-V features that are currenly enabled! Halting!"
                        $global:FunctionResult = "1"
                        Pop-Location
                        return
                    }
                }
                else {
                    Write-Error $_
                    Write-Host "Errors for the Deploy-HyperVVagrantBoxManually function are as follows:"
                    Write-Error $($DVMErr | Out-String)

                    if ($DeployVMResult.VMName -ne $null) {
                        $DestroyVMOutput = Manage-HyperVVM -VmName $DeployVMResult.VMName -Destroy -ErrorAction SilentlyContinue
                    }
                    if ($DeployVMResult.ExternalSwitchCreated -eq $True) {
                        Remove-VMSwitch "ToExternal" -Force -ErrorAction SilentlyContinue
                    }
                    if ($DeployVMResult.TempHyperVVMLocation -ne $null) {
                        if (Test-Path $DeployVMResult.TempHyperVVMLocation) {
                            Remove-Item $DeployVMResult.TempHyperVVMLocation -Recurse -Force
                        }
                    }
                    if ($DeployVMResult.BoxFileLocation -ne $null) {
                        if (Test-Path $DeployVMResult.BoxFileLocation) {
                            Remove-Item $DeployVMResult.BoxFileLocation -Force
                        }
                    }
                    
                    $global:FunctionResult = "1"
                    Pop-Location
                    return
                }
            }
        }
        else {
            Write-Error $_
            Write-Host "Errors for the Deploy-HyperVVagrantBoxManually function are as follows:"
            Write-Error $($DVMErr | Out-String)

            if ($DeployVMResult.VMName -ne $null) {
                $DestroyVMOutput = Manage-HyperVVM -VmName $DeployVMResult.VMName -Destroy -ErrorAction SilentlyContinue
            }
            if ($DeployVMResult.ExternalSwitchCreated -eq $True) {
                Remove-VMSwitch "ToExternal" -Force -ErrorAction SilentlyContinue
            }
            if ($DeployVMResult.TempHyperVVMLocation -ne $null) {
                if (Test-Path $DeployVMResult.TempHyperVVMLocation) {
                    Remove-Item $DeployVMResult.TempHyperVVMLocation -Recurse -Force
                }
            }
            if ($DeployVMResult.BoxFileLocation -ne $null) {
                if (Test-Path $DeployVMResult.BoxFileLocation) {
                    Remove-Item $DeployVMResult.BoxFileLocation -Force
                }
            }
            
            $global:FunctionResult = "1"
            Pop-Location
            return
        }
    }

    if (!$VMDeployed -and $DeployVMResult -eq "RestartNeeded") {
        if (!$AllowRestarts) {
            Write-Warning "You must restart $env:ComputerName before the TestHyperVExternalvSwitch can proceed! Halting!"
            # IMPORTANT NOTE: The below Write-Output "RestartNeeded" is necessary
            Write-Output "RestartNeeded"
            $global:FunctionResult = "1"
            Pop-Location
            return
        }
        else {
            Restart-Computer -Confirm:$False -Force
        }
    }

    if ($VMDeployed) {
        try {
            $CustomCentOSVM = Get-VM -Name $DeployVMResult.VMName

            if ($CustomCentOSVM.State -eq "Running") {
                # Give the CentOS7 VM 120 seconds to report its IP
                $counter = 0
                while (!$VMIPv4Address -and $counter -le 4) {
                    $counter++
                    Write-Host "Waiting for $($CustomCentOSVM.Name) to report its IP Address..."
                    Start-Sleep -Seconds 30
                    $VMIPv4Address = $(Get-VMNetworkAdapter -VMName $CustomCentOSVM.Name).IPAddresses | Where-Object {TestIsValidIPAddress -IPAddress $_}
                }

                if ($VMIPv4Address) {
                    # If $HOME\.ssh\known_hosts already exists, make sure there is NOT a line in there already regarding $VMIPv4Address
                    # If there is, remove it
                    if (Test-Path "$HOME\.ssh\known_hosts") {
                        $KnownHostsContent = Get-Content "$HOME\.ssh\known_hosts"
                        $PotentialLineToRemove = $KnownHostsContent -match $VMIPv4Address
                        if ($PotentialLineToRemove) {
                            $UpdatedKnownHostsContent = $KnownHostsContent | Where-Object {$_ -ne $PotentialLineToRemove}
                            Set-Content -Path "$HOME\.ssh\known_hosts" -Value $UpdatedKnownHostsContent
                        }
                    }

                    # Now we can start issuing ssh commands through our CentOS7 VM to see if the External vSwitch actually works
                    # If it does, we know that MacAddress spoofing is enabled on the hypervisor and that the hypervisor is
                    # most likely Hyper-V
                    $PingRouterResult = ssh -o "StrictHostKeyChecking=no" -o "IdentitiesOnly=yes" -i "$VagrantInsecurePrivKeyPath" -t vagrant@$VMIPv4Address "ping -c 1 '$NextHop' >/dev/null && echo True" 2>$null
                    if ($PingRouterResult -eq "True") {$CanReachRouter = $True}
                    $PingInternetResult = ssh -o "StrictHostKeyChecking=no" -o "IdentitiesOnly=yes" -i "$VagrantInsecurePrivKeyPath" -t vagrant@$VMIPv4Address "ping -c 1 '8.8.8.8' >/dev/null && echo True" 2>$null
                    if ($PingInternetResult -eq $True) {$CanReachInternet = $True}

                    if (!$CanReachInternet -and !$CanReachRouter) {
                        $ExternalvSwitchWorks = $False
                    }
                    else {
                        Write-Host "External vSwitch $($(Get-VMSwitch -SwitchType External).Name) can reach the internet!" -ForegroundColor Green
                        $ExternalvSwitchWorks = $True
                    }
                }
                else {
                    $MacAddressSpoofingEnabled = $False
                }
            }
        }
        catch {
            Write-Error $_

            if ($DeployVMResult.VMName -ne $null) {
                $DestroyVMOutput = Manage-HyperVVM -VmName $DeployVMResult.VMName -Destroy -ErrorAction SilentlyContinue
            }
            if ($DeployVMResult.ExternalSwitchCreated) {
                Remove-VMSwitch "ToExternal" -Force -ErrorAction SilentlyContinue
            }
            if ($DeployVMResult.TempHyperVVMLocation -ne $null) {
                if (Test-Path $DeployVMResult.TempHyperVVMLocation) {
                    Remove-Item $DeployVMResult.TempHyperVVMLocation -Recurse -Force
                }
            }
            if ($DeployVMResult.BoxFileLocation -ne $null) {
                if (Test-Path $DeployVMResult.BoxFileLocation) {
                    Remove-Item $DeployVMResult.BoxFileLocation -Force
                }
            }

            $global:FunctionResult = "1"
            Pop-Location
            return
        }
    }

    # Cleanup
    if ($DeployVMResult.VMName -ne $null) {
        $DestroyVMOutput = Manage-HyperVVM -VmName $DeployVMResult.VMName -Destroy -ErrorAction SilentlyContinue
    }
    if ($DeployVMResult.ExternalSwitchCreated) {
        Remove-VMSwitch "ToExternal" -Force -ErrorAction SilentlyContinue
    }
    if ($DeployVMResult.TempHyperVVMLocation -ne $null) {
        if (Test-Path $DeployVMResult.TempHyperVVMLocation) {
            Remove-Item $DeployVMResult.TempHyperVVMLocation -Recurse -Force
        }
    }
    if ($DeployVMResult.BoxFileLocation -ne $null) {
        if (Test-Path $DeployVMResult.BoxFileLocation) {
            Remove-Item $DeployVMResult.BoxFileLocation -Force
        }
    }

    $Output = [ordered]@{
        ExternalvSwitchWorks    = if ($ExternalvSwitchWorks -eq $null -or $ExternalvSwitchWorks -eq $False) {$False} else {$True}
        CanReachInternet        = if ($CanReachInternet -eq $null -or $CanReachInternet -eq $False) {$False} else {$True}
        CanReachRouter          = if ($CanReachRouter -eq $null -or $CanReachRouter -eq $False) {$False} else {$True}
    }

    if ($IsVirtual) {
        if ($VMDeployed) {
            $Output.Add("VirtualizationExtensionsExposed",$True)
        }
        else {
            $Output.Add("VirtualizationExtensionsExposed",$False)
        }

        if ($MacAddressSpoofingEnabled -eq $False) {
            $Output.Add("MacAddressSpoofingEnabled",$False)
        }
        elseif ($ExternalvSwitchWorks -eq $True) {
            $Output.Add("MacAddressSpoofingEnabled",$True)
        }
        elseif ($VirtualizationExtensionsExposed -eq $False -or $VirtualizationExtensionsExposed -eq $null) {
            $Output.Add("MacAddressSpoofingEnabled","Unknown")
        }
        else {
            $Output.Add("MacAddressSpoofingEnabled",$False)
        }
    }

    [pscustomobject]$Output

    Pop-Location
    
    #endregion >> Main Body
}