Private/EnableNestedVM.ps1

function EnableNestedVM {
    [CmdletBinding(DefaultParameterSetName='Default')]
    Param(
        [Parameter(
            Mandatory = $False,
            ParameterSetName = 'Default'
        )]
        [string]$TargetHostNameOrIP,

        [Parameter(
            Mandatory=$True,
            ParameterSetName = 'UsingVMName'
        )]
        [string]$TargetVMName,

        [Parameter(Mandatory=$False)]
        [string]$HypervisorFQDNOrIP,

        [Parameter(Mandatory=$False)]
        $TargetHostNameCreds,

        [Parameter(Mandatory=$False)]
        $HypervisorCreds,

        # -NoMacAddressSpoofing WILL result in creating a Local NAT with an Internal vSwitch on the
        # Target Machine (assuming it's a Guest VM). Maybe change this parameter to 'CreateNAT' instead
        # of 'NoMacAddressSpoofing'
        [Parameter(Mandatory=$False)]
        [switch]$NoMacAddressSpoofing,

        # -TryWithoutHypervisorInfo MIGHT result in creating a Local NAT
        # with an Internal vSwitch on the Target Machine (assuming it's a Guest VM). It depends if
        # Get-NestedVirtCapabilities detemines whether the Target Machine can use an External vSwitch or not.
        # If it can, then a Local NAT will NOT be created.
        # If a NAT already exists on the Target Machine, that NAT will be changed to
        # 10.0.75.0/24 with IP 10.0.75.1 if it isn't already
        [Parameter(Mandatory=$False)]
        [switch]$TryWithoutHypervisorInfo,

        # If used along with $TryWithoutHypervisorInfo, if
        # $GuestVMNestedVirtCapabilties.NetworkingPossibilities -eq "Mac Address Spoofing", creates a Local NAT
        # on the TargetMachine (as opposed to assuming Mac Address Spoofing is enabled on the Hyper-V hypervisor
        # for the Guest VM and using an External vSwitch)
        # IMPORANT: If a NAT already exists on the Target Machine, that NAT will be changed to
        # 10.0.75.0/24 with IP 10.0.75.1 if it isn't already
        [Parameter(Mandatory=$False)]
        [switch]$Force,

        [Parameter(Mandatory=$False)]
        [string]$NATIP,

        [Parameter(Mandatory=$False)]
        [ValidateRange(24,31)]
        [int]$NATNetworkMask,

        [Parameter(Mandatory=$False)]
        [string]$NATName,

        [Parameter(
            Mandatory=$True,
            ParameterSetName = 'InfoAlreadyCollected'
        )]
        $GuestVMAndHVInfo, # Uses output of Get-GuestVMAndHypervisorInfo function

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

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

        [Parameter(Mandatory=$False)]
        [ValidateScript({
            $(($_ % 4) -eq 0) -and $($_ -ge 4)
        })]
        [int]$GuestVMMemoryInGB,

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

    ##### BEGIN Variable/Parameter Transforms and PreRun Prep #####

    if (!$(GetElevation)) {
        Write-Error "This function must be used from an Elevated PowerShell Session (i.e. Run As Administrator)! Halting!"
        $global:FunctionResult = "1"
        return
    }

    if ($PSBoundParameters['TargetHostNameCreds']) {
        if ($TargetHostNameCreds.GetType().FullName -ne "System.Management.Automation.PSCredential") {
            Write-Error "The object provided to the -TargetHostNameCreds parameter must be a System.Management.Automation.PSCredential! Halting!"
            $global:FunctionResult = "1"
            return
        }
    }
    if ($PSBoundParameters['HypervisorCreds']) {
        if ($HypervisorCreds.GetType().FullName -ne "System.Management.Automation.PSCredential") {
            Write-Error "The object provided to the -HypervisorCreds parameter must be a System.Management.Automation.PSCredential! Halting!"
            $global:FunctionResult = "1"
            return
        }
    }

    if ($NATIP) {
        if (!$(TestIsValidIPAddress -IPAddress $NATIP)) {
            Write-Error "$NATIP is NOT a valid IPv4 IP Address! Halting!"
            $global:FunctionResult = "1"
            return
        }
    }

    if ($Force -and !$TryWithoutHypervisorInfo) {
        Write-Error "The -Force switch should only be used if the -TryWithoutHypervisor info switch is also used! Halting!"
        $global:FunctionResult = "1"
        return
    }

    if (!$GuestVMAndHVInfo) {
        if (!$TargetHostNameOrIP -and !$TargetVMName) {
            $TargetHostNameOrIP = $env:ComputerName
        }

        try {
            $HostNameNetworkInfo = ResolveHost -HostNameOrIP $TargetHostNameOrIP
        }
        catch {
            Write-Error $_
            $global:FunctionResult = "1"
            return
        }

        $GetWorkingCredsSplatParams = @{
            RemoteHostNameOrIP          = $HostNameNetworkInfo.FQDN
            ErrorAction                 = "Stop"
        }
        if ($TargetHostNameCreds) {
            $GetWorkingCredsSplatParams.Add("AltCredentials",$TargetHostNameCreds)
        }
        
        try {
            $GetTargetHostCredsInfo = GetWorkingCredentials @GetWorkingCredsSplatParams
            if (!$GetTargetHostCredsInfo.DeterminedCredsThatWorkedOnRemoteHost) {throw "Can't determine working credentials for $($HostNameNetworkInfo.FQDN)!"}
            
            if ($GetTargetHostCredsInfo.CurrentLoggedInUserCredsWorked -eq $True) {
                $TargetHostNameCreds = $null
            }

            $TargetHostInvCmdLocation = $GetTargetHostCredsInfo.RemoteHostWorkingLocation
        }
        catch {
            Write-Error $_
            if ($PSBoundParameters['TargetHostNameCreds']) {
                Write-Error "The Get-WorkingCredentials function failed! Check the credentials provided to the -TargetHostNameCreds parameter! Halting!"
            }
            else {
                Write-Error "The Get-WorkingCredentials function failed! Try using the -TargetHostNameCreds parameter! Halting!"
            }
            $global:FunctionResult = "1"
            return
        }
    }

    if (![bool]$PSBoundParameters['GuestVMAndHVInfo']) {
        $GetVMAndHVSplatParams = @{}
        
        if ($($TargetHostNameOrIP -or $TargetHostInvCmdLocation) -and $GetVMAndHVSplatParams.Keys -notcontains "TargetHostNameOrIP") {
            if ($TargetHostInvCmdLocation) {
                $GetVMAndHVSplatParams.Add("TargetHostNameOrIP",$TargetHostInvCmdLocation)
            }
            elseif ($TargetHostNameOrIP) {
                $GetVMAndHVSplatParams.Add("TargetHostNameOrIP",$TargetHostNameOrIP)
            }
        }
        elseif ($TargetVMName -and $GetVMAndHVSplatParams.Keys -notcontains "TargetVMName") {
            $GetVMAndHVSplatParams.Add("TargetVMName",$TargetVMName)
        }

        if ($TargetHostNameCreds -and $GetVMAndHVSplatParams.Keys -notcontains "TargetHostNameCreds") {
            $GetVMAndHVSplatParams.Add("TargetHostNameCreds",$TargetHostNameCreds)
        }

        if ($($HypervisorFQDNOrIP -or $HypervisorInvCmdLocation) -and $GetVMAndHVSplatParams.Keys -notcontains "HypervisorFQDNOrIP") {
            if ($HypervisorInvCmdLocation) {
                $GetVMAndHVSplatParams.Add("HypervisorFQDNOrIP",$HypervisorInvCmdLocation)
            }
            elseif ($HypervisorFQDNOrIP) {
                $GetVMAndHVSplatParams.Add("HypervisorFQDNOrIP",$HypervisorFQDNOrIP)
            }
        }

        if ($HypervisorCreds -and $GetVMAndHVSplatParams.Keys -notcontains "HypervisorCreds") {
            $GetVMAndHVSplatParams.Add("HypervisorCreds",$HypervisorCreds)
        }
        
        if ($($TryWithoutHypervisorInfo -and $GetVMAndHVSplatParams.Keys -notcontains "TryWithoutHypervisorInfo") -or 
        $($(ConfirmAWSVM -EA SilentlyContinue) -or $(ConfirmAzureVM -EA SilentlyContinue) -or
        $(ConfirmGoogleComputeVM -EA SilentlyContinue))
        ) {
            $GetVMAndHVSplatParams.Add("TryWithoutHypervisorInfo",$True)
        }

        if ($AllowRestarts -and $GetVMAndHVSplatParams.Keys -notcontains "AllowRestarts") {
            $GetVMAndHVSplatParams.Add("AllowRestarts",$True)
        }

        if ($NoMacAddressSpoofing -and $GetVMAndHVSplatParams.Keys -notcontains "NoMacAddressSpoofing") {
            $GetVMAndHVSplatParams.Add("NoMacAddressSpoofing",$True)
        }

        if ($SkipHyperVInstallCheck -and $GetVMAndHVSplatParams.Keys -notcontains "SkipHyperVInstallCheck") {
            $GetVMAndHVSplatParams.Add("SkipHyperVInstallCheck",$True)
        }

        if ($SkipExternalvSwitchCheck -and $GetVMAndHVSplatParams.Keys -notcontains "SkipExternalvSwitchCheck") {
            $GetVMAndHVSplatParams.Add("SkipExternalvSwitchCheck",$True)
        }

        try {
            $GuestVMAndHVInfo = Get-GuestVMAndHypervisorInfo @GetVMAndHVSplatParams -ErrorAction SilentlyContinue -ErrorVariable GGIErr
            if (!$GuestVMAndHVInfo) {throw "The Get-GuestVMAndHypervisorInfo function failed! Halting!"}

            if ($PSVersionTable.PSEdition -eq "Core") {
                $GetPendingRebootAsString = ${Function:GetPendingReboot}.Ast.Extent.Text
                
                $RebootPendingCheck = Invoke-WinCommand -ComputerName localhost -ScriptBlock {
                    Invoke-Expression $args[0]
                    $(GetPendingReboot).RebootPending
                } -ArgumentList $GetPendingRebootAsString

                $RebootPendingFileCheck = Invoke-WinCommand -ComputerName localhost -ScriptBlock {
                    Invoke-Expression $args[0]
                    $(GetPendingReboot).PendFileRenVal
                } -ArgumentList $GetPendingRebootAsString
            }
            else {
                $RebootPendingCheck = $(GetPendingReboot).RebootPending
                $RebootPendingFileCheck = $(GetPendingReboot).PendFileRenVal
            }

            if ($GuestVMAndHVInfo.RestartNeeded -and $RebootPendingCheck -and $RebootPendingFileCheck -ne $null -and !$AllowRestarts) {
                Write-Verbose "You might need to restart $env:ComputerName before the GetNestedVirtCapabilities function can proceed! Halting!"
            }
        }
        catch {
            Write-Error $_
            if ($($GGIErr | Out-String) -match "The operation has timed out") {
                Write-Error "There was a problem downloading the Vagrant Box to be used to test the External vSwitch! Halting!"
                $global:FunctionResult = "1"
                return
            }
            else {
                Write-Host "Errors for the Get-GuestVMAndHypervisorInfo function are as follows:"
                Write-Error $($GGIErr | Out-String)
                $global:FunctionResult = "1"
                return
            }
        }
    }
    else {
        $ValidGuestVMAndHVInfoNoteProperties = @(
            "HypervisorNetworkInfo"
            "HypervisorInvCmdLocation"
            "HypervisorComputerInfo"
            "HypervisorOSInfo"
            "TargetVMInfoFromHyperV"
            "VMProcessorInfo"
            "VMNetworkAdapterInfo"
            "VMMemoryInfo"
            "HypervisorCreds"
            "HostNameNetworkInfo"
            "TargetHostInvCmdLocation"
            "HostNameComputerInfo"
            "HostNameOSInfo"
            "HostNameProcessorInfo"
            "HostNameBIOSInfo"
            "TargetHostNameCreds"
            "RestartNeeded"
            "RestartOccurred"
            "VirtualizationExtensionsExposed"
            "MacAddressSpoofingEnabled"
        )
        [System.Collections.ArrayList]$FoundIssueWithGuestVMAndHVInfo = @()
        $ParamObjMembers = $($GuestVMAndHVInfo | Get-Member -MemberType NoteProperty).Name
        foreach ($noteProp in $ParamObjMembers) {
            if ($ValidGuestVMAndHVInfoNoteProperties -notcontains $noteProp) {
                $null = $FoundIssueWithGuestVMAndHVInfo.Add($noteProp)
            }
        }
        if ($FoundIssueWithGuestVMAndHVInfo.Count -gt 3) {
            $ParamObjMembers
            Write-Error "The object provided to the -GuestVMAndHVInfo parameter is invalid! It must be output from the Get-GuestVMAndHypervisorInfo function! Halting!"
            $global:FunctionResult = "1"
            return
        }
    }

    if (!$GuestVMAndHVInfo) {
        Write-Error "There was a problem with the Get-GuestVMandHypervisorInfo function! Halting!"
        $global:FunctionResult = "1"
        return
    }

    if (!$HypervisorCreds -and !$GuestVMAndHVInfo.HypervisorCreds -and 
    $($GuestVMAndHVInfo.HypervisorComputerInfo -eq $null -or
    $GuestVMAndHVInfo.HypervisorNetworkInfo -eq $null -or
    $GuestVMAndHVInfo.HypervisorOSInfo -eq $null -or
    $(ConfirmAWSVM -EA SilentlyContinue) -or $(ConfirmAzureVM -EA SilentlyContinue) -or
    $(ConfirmGoogleComputeVM -EA SilentlyContinue))
    ) {
        $TryWithoutHypervisorInfo = $True
    }

    if ($GuestVMAndHVInfo.HypervisorCreds -ne $null) {
        $HypervisorCreds = $GuestVMAndHVInfo.HypervisorCreds
    }
    if ($GuestVMAndHVInfo.TargetHostNameCreds -ne $null) {
        $TargetHostNameCreds = $GuestVMAndHVInfo.TargetHostNameCreds
        if ($TargetHostNameCreds -eq $(whoami)) {
            $TargetHostNameCreds = $null
        }
    }

    try {
        $GuestNestedVirtCapabiltiesSplatParams = @{
            ErrorAction         = "SilentlyContinue"
            ErrorVariable       = "GNCErr"
            WarningAction       = "SilentlyContinue"
            GuestVMAndHVInfo    = $GuestVMAndHVInfo
        }
        if ($TryWithoutHypervisorInfo) {
            $GuestNestedVirtCapabiltiesSplatParams.Add("TryWithoutHypervisorInfo",$True)
        }
        if ($SkipHyperVInstallCheck) {
            $GuestNestedVirtCapabiltiesSplatParams.Add("SkipHyperVInstallCheck",$True)
        }

        $GuestVMNestedVirtCapabilties = GetNestedVirtCapabilities @GuestNestedVirtCapabiltiesSplatParams
        if (!$GuestVMNestedVirtCapabilties) {throw "The Get-NestedVirtCapabiltiies function failed! Halting!"}
    }
    catch {
        Write-Error $_
        Write-Host "Errors from the GetNestedVirtCapabilities function are as follows:"
        Write-Error $($GNCErr | Out-String)
        $global:FunctionResult = "1"
        return
    }

    if ($GuestVMNestedVirtCapabilties.NestedVirtualizationPossible -eq $False) {
        if ($TryWithoutHypervisorInfo -and !$Force) {
            Write-Warning "Nested Virtualization is NOT possible without access to the hypervisor. Steps to remediate are as follows:"
            $GuestVMNestedVirtCapabilties.StepsToAllow64BitNestedVMs
            $global:FunctionResult = "1"
            return
        }
    }

    # Determine where this function is being run
    if ($env:ComputerName -eq $GuestVMAndHVInfo.HypervisorNetworkInfo.HostName) {
        $Locale = "Hypervisor"
    }
    elseif ($env:ComputerName -eq $GuestVMAndHVInfo.HostNameNetworkInfo.HostName) {
        $Locale = "GuestVM"
    }
    else {
        $Locale = "Elsewhere"
    }

    if (!$TryWithoutHypervisorInfo) {
        if ($AllowRestarts -and $Locale -eq "GuestVM" -and !$SkipPrompt) {
            Write-Warning "The Guest VM (i.e. this computer - $env:ComputerName) might need to be restarted in order to enable Nested Virtualization!"
            $ContinueChoice = Read-Host -Prompt "Are you sure you want to continue? [Yes\No]"
            if ($ContinueChoice -notmatch "Yes|yes|Y|y") {
                Write-Error "User chose not to proceed! Halting!"
                $global:FunctionResult = "1"
                return
            }
        }

        $VMInfo = $GuestVMAndHVInfo.TargetVMInfoFromHyperV
        $VMName = $VMInfo.VMName
        $VMProcessorInfo = $GuestVMAndHVInfo.VMProcessorInfo
        $VMNetworkAdapterInfo = $GuestVMAndHVInfo.VMNetworkAdapterInfo
        $VMMemoryInfo = $GuestVMAndHVInfo.VMMemoryInfo

        # Add some additional properties to $VMInfo
        Add-Member -InputObject $VMInfo NoteProperty -Name "ExposeVirtualizationExtensions" -Value $VMProcessorInfo.ExposeVirtualizationExtensions -Force
        Add-Member -InputObject $VMInfo NoteProperty -Name "SnapshotEnabled" -Value $false -Force
        Add-Member -InputObject $VMInfo NoteProperty -Name "MacAddressSpoofing" -Value $VMNetworkAdapterInfo.MacAddressSpoofing -Force
        Add-Member -InputObject $VMInfo NoteProperty -Name "MemorySize" -Value $VMMemoryInfo.Startup -Force
    }

    # Constants
    $4GB = 4294967296

    if ($GuestVMMemoryInGB) {
        $Factor = $GuestVMMemoryInGB / 4
        $FinalMem = $4GB * $Factor
    }
    elseif ($VMMemoryInfo) {
        if ($VMMemoryInfo.Startup -lt $4GB) {
            $FinalMem = $4GB
        }
        else {
            $FinalMem = $VMMemoryInfo.Startup
        }
    }
    else {
        $FinalMem = $4GB
    }
    $FinalMemRounded = ConvertSize -From Bytes -To GB -Value $FinalMem

    ##### END Variable/Parameter Transforms and PreRun Prep #####


    ##### BEGIN Main Body #####

    if (!$TryWithoutHypervisorInfo) {
        #Write-Host "`nThis function will set the following for $VMName in order to enable nesting:"
        Write-Host ""

        $prompt = $false
        
        [System.Collections.ArrayList]$NeededChanges = @()
        [System.Collections.ArrayList]$AttemptedChanges = @()
        [System.Collections.ArrayList]$UnsatisfiedChanges = @()
        # Output text for proposed actions
        if ($VMInfo.State -eq 'Saved') {
            if ($AllowRestarts) {
                Write-Host "Vm State: $($VMInfo.State)" -ForegroundColor yellow
                Write-Warning "$VMName will be restarted"
                Write-Warning "Saved state will be removed"
                $prompt = $true
                $null = $AttemptedChanges.Add("RemoveSavedState")
                if ($AttemptedChanges -notcontains "Restart") {
                    $null = $AttemptedChanges.Add("Restart")
                }
            }
            else {
                $WarnMsgAllowRestarts = "The -AllowRestarts parameter was NOT used. The VM $VMName needs to " +
                "be turned OFF in order to remove the Saved state! Please do so manually on the Hyper-V Host via: " +
                "`n Stop-VM -VMName $VMName`n Remove-VMSavedState -VMName $VMName"
                Write-Warning $WarnMsgAllowRestarts
                $null = $UnsatisfiedChanges.Add("RemoveSavedState")
            }
            $null = $NeededChanges.Add("RemoveSavedState")
            if ($NeededChanges -notcontains "Restart") {
                $null = $NeededChanges.Add("Restart")
            }
        }
        if ($VMInfo.DynamicMemoryEnabled -eq $true) {
            if ($AllowRestarts) {
                Write-Warning "$VMName will be restarted"
                Write-Warning "Dynamic memory will be disabled"
                $prompt = $true
                $null = $AttemptedChanges.Add("DisableDynamicMemory")
                if ($AttemptedChanges -notcontains "Restart") {
                    $null = $AttemptedChanges.Add("Restart")
                }
            }
            else {
                $WarnMsgAllowRestarts1 = "The -AllowRestarts parameter was NOT used. Dynamic memory for the VM " +
                "$VMName needs to be disabled. Please do so manually on the Hyper-V Host while $VMName is OFF via:" +
                "`n Stop-VM -VMName $VMName`n Set-VMMemory -VMName $VMName -DynamicMemoryEnabled `$false"
                Write-Warning $WarnMsgAllowRestarts1
                $null = $UnsatisfiedChanges.Add("DisableDynamicMemory")
                if ($UnsatisfiedChanges -notcontains "Restart") {
                    $null = $UnsatisfiedChanges.Add("Restart")
                }
            }
            $null = $NeededChanges.Add("DisableDynamicMemory")
            if ($NeededChanges -notcontains "Restart") {
                $null = $NeededChanges.Add("Restart")
            }
        }
        if ($VMInfo.ExposeVirtualizationExtensions -eq $false) {
            if ($AllowRestarts) {
                Write-Warning "$VMName will be restarted"
                Write-Warning "Virtualization extensions will be enabled"
                $prompt = $true
                $null = $AttemptedChanges.Add("ExposeVirtualizationExtensions")
                if ($AttemptedChanges -notcontains "Restart") {
                    $null = $AttemptedChanges.Add("Restart")
                }
            }
            else {
                $WarnMsgAllowRestarts2 = "The -AllowRestarts parameter was NOT used. Virtualization extensions for the " +
                "VM $VMName needs to be exposed. Please do so manually on the Hyper-V Host while $VMName is OFF via:" +
                "`n Stop-VM -VMName $VMName`n Set-VMProcessor -VMName $VMName -ExposeVirtualizationExtensions `$true"
                Write-Warning $WarnMsgAllowRestarts2
                $null = $UnsatisfiedChanges.Add("ExposeVirtualizationExtensions")
                if ($UnsatisfiedChanges -notcontains "Restart") {
                    $null = $UnsatisfiedChanges.Add("Restart")
                }
            }
            $null = $NeededChanges.Add("ExposeVirtualizationExtensions")
            if ($NeededChanges -notcontains "Restart") {
                $null = $NeededChanges.Add("Restart")
            }
        }
        if ($VMInfo.MacAddressSpoofing -eq 'Off' -and !$NoMacAddressSpoofing) {
            Write-Warning "MAC address spoofing will be enabled."
            $prompt = $true
            $null = $NeededChanges.Add("TurnMacSpoofingOn")
            $null = $AttemptedChanges.Add("TurnMacSpoofingOn")
        }
        if ($VMInfo.MemorySize -ne $FinalMem) {
            Write-Warning "VM memory will be set to $FinalMemRounded GB"
            $prompt = $true
            $null = $NeededChanges.Add("AdjustStartupMemory")
            $null = $AttemptedChanges.Add("AdjustStartupMemory")
        }
        if ($VMInfo.ProcessorCount -lt 2) {
            Write-Warning "VM Processor Count will be set to 2"
            $prompt = $true
            $null = $NeededChanges.Add("UpProcessorCount")
            $null = $AttemptedChanges.Add("UpProcessorCount")
        }
        if ($GuestVMNestedVirtCapabilties.NetworkingPossibilities -contains "Mac Address Spoofing" -and !$NoMacAddressSpoofing) {
            Write-Warning "Mac Address Spoofing is enabled. We will NOT create a NAT interface on the Guest VM. No action taken."
        }
        if ($NoMacAddressSpoofing -or $NATIP -or $NATNetworkMask) {
            if (!$NATIP) {
                $NATIP = "10.0.75.1"
            }
            if (!$NATNetworkMask) {
                $NATNetworkMask = 24
            }
            $IPRange = Get-IPRange -ip $NATIP -cidr $NATNetworkMask
            $NATSubnet = "$($IPRange[0])/$NATNetworkMask"

            $WarnMsg1 = "On $VMName, if an Internal vSwitch called 'LocalNAT' does NOT already exist and if NO OTHER Network Adapter " +
            "is using subnet $NATSubnet with IP $NATIP, then an Internal vSwitch called 'LocalNAT' will be created"
            Write-Warning $WarnMsg1
            $prompt = $True
        }

        if ($prompt) {
            if (!$SkipPrompt) {
                Write-Host ""
                $char = Read-Host -Prompt "Do you agree to all of these changes to VM $VMName`? [Yes/No]"
                while ($char -notmatch "Yes|yes|Y|y|No|no|N|n") {
                    Write-Host "Invalid Input, Y or N"
                    $char = Read-Host -Prompt "Do you agree to all of these changes to VM $VMName`? [Yes/No]"
                }
            }
            else {
                $char = "Yes"
            }
        }

        if ($char -match "Yes|yes|Y|y") {
            [System.Collections.ArrayList]$VMSettingsThatWereChanged = @()

            if ($NoMacAddressSpoofing) {
                if ($Locale -eq "GuestVM") {
                    if (!$SkipHyperVInstallCheck) {
                        # Install Hyper-V Features if they haven't aready

                        # NOTE: Below $HyperVFeaturesInstallResults contains properties 'InstallResults' (array of InstallFeatureDism
                        # pscustomobjects which contiain properties contains properties [string]Path, [bool]Online, [string]WinPath,
                        # [string]SysDrivePath, [bool]RestartNeeded, [string]$LogPath, [string]ScratchDirectory,
                        # [string]LogLevel), and 'InstallFailures' (array of strings of Dism Feature Names that
                        # failed to install).
                        # NOTE: InstallHyperVFeatures returns $null if everything is already installed.
                        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"
                            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"
                            return
                        }

                        if ($HyperVFeaturesInstallResults.InstallFailures.Count -gt 0) {
                            Write-Error "Please remedy the Hyper-V Features that failed to install before proceeding. Halting!"
                            $global:FunctionResult = "1"
                            return
                        }
                        
                        if ($HyperVFeaturesInstallResults.InstallResults.RestartNeeded -notcontains $True -and !$InstallContainersFeatureDismResult.RestartNeeded) {
                            Write-Host "All dependencies are already installed...proceeding..." -ForegroundColor Green
                        }
                        else {
                            if ($HyperVFeaturesInstallResults.InstallResults.RestartNeeded -contains $True -or $InstallContainersFeatureDismResult.RestartNeeded) {
                                if ($AllowRestarts) {
                                    Write-Host "Shutting down $env:ComputerName..."
                                    # NOTE: The below output "Restarting" is important when running this function via Invoke-Command
                                    $null = $VMSettingsThatWereChanged.Add("HyperVInstall")
                                    $null = $VMSettingsThatWereChanged.Add("Restart")
                                    Stop-Computer -Confirm:$false
                                }
                                else {
                                    Write-Error "You must restart $env:ComputerName before proceeding! Halting!"
                                    return
                                }
                            }
                        }
                    }

                    try {
                        if ($(Get-Module -ListAvailable).Name -notcontains "Hyper-V" -and $(Get-Module).Name -notcontains "Hyper-V") {
                            throw "Hyper-V does NOT appear to be installed on $env:ComputerName! Halting!"
                        }
                        # NOTE: New-VMSwitch is the cmdlet that actually creates the Network Adapter called 'vEthernet ($NATName)'
                        # New-NetNat is another type of object that we just happen to be calling the same thing (i.e. '$NATName'),
                        # but it doesn't HAVE to be named the same thing.

                        # NOTE: 10.075.0/24 is the default Docker For Windows (i.e. Docker CE) NAT subnet. Figured we might as well
                        # use the same if one isn't provided to the is function.
                        if (!$NATIP) {
                            $NATIP = "10.0.75.1"
                        }
                        if (!$NATNetworkMask) {
                            $NATNetworkMask = 24
                        }
                        if (!$NATName) {
                            $NATName = "LocalNAT"
                        }
                        $NATSubnet = "$NATIP/$NATNetworkMask"

                        [System.Collections.ArrayList]$ExistingvSwitchInfo = @()
                        if ([bool]$(GetvSwitchAllRelatedInfo -IPAddress $NATIP -ErrorAction SilentlyContinue -WarningAction SilentlyContinue)) {
                            $vSwitchInfoByIP = GetvSwitchAllRelatedInfo -IPAddress $NATIP -ErrorAction SilentlyContinue -WarningAction SilentlyContinue

                            if ($vSwitchInfoByIP) {
                                $null = $ExistingvSwitchInfo.Add($vSwitchInfoByIP)
                            }
                        }
                        if ([bool]$(GetvSwitchAllRelatedInfo -vSwitchName $NATName -ErrorAction SilentlyContinue -WarningAction SilentlyContinue)) {
                            $vSwitchInfoByName = GetvSwitchAllRelatedInfo -vSwitchName $NATName -ErrorAction SilentlyContinue -WarningAction SilentlyContinue

                            if ($vSwitchInfoByName) {
                                $null = $ExistingvSwitchInfo.Add($vSwitchInfoByName)
                            }
                        }

                        if ($ExistingvSwitchInfo.Count -eq 0) {
                            if ($PSVersionTable.PSEdition -eq "Core") {
                                Invoke-WinCommand -ComputerName localhost -ScriptBlock {
                                    $null = New-NetNat -Name $args[0] -InternalIPInterfaceAddressPrefix $args[1]
                                    $null = New-VMSwitch -Name $args[0] -SwitchType Internal
                                    $null = Get-NetAdapter "vEthernet ($($args[0]))" | New-NetIPAddress -IPAddress $args[2] -AddressFamily IPv4 -PrefixLength $args[3]
                                } -ArgumentList $NATName,$NATSubnet,$NATIP,$NATNetworkMask
                            }
                            else {
                                $null = New-NetNat -Name $NATName -InternalIPInterfaceAddressPrefix $NATSubnet
                                $null = New-VMSwitch -Name $NATName -SwitchType Internal
                                $null = Get-NetAdapter "vEthernet ($NATName)" | New-NetIPAddress -IPAddress $NATIP -AddressFamily IPv4 -PrefixLength $NATNetworkMask
                            }
                        
                            $null = $NeededChanges.Add("NetworkAddressTranslation")
                            $null = $AttemptedChanges.Add("NetworkAddressTranslation")
                            $null = $VMSettingsThatWereChanged.Add("NetworkAddressTranslation")
                        }
                        else {
                            $null = $NeededChanges.Add("None - LocalNAT Already Exists")
                        }
                    }
                    catch {
                        Write-Error $_
                        Write-Warning "Failed to create 'vEthernet ($NATName)'! However, it is possible that Mac Address Spoofing is enabled on this Guest VM, in which case NAT is not needed."
                    }
                }
                elseif ($Locale -match "Hypervisor|Elsewhere") {
                    $FunctionsForRemoteUse = @(
                        ${Function:InstallFeatureDism}.Ast.Extent.Text
                        ${Function:InstallHyperVFeatures}.Ast.Extent.Text
                        ${Function:TestIsValidIPAddress}.Ast.Extent.Text
                        ${Function:GetvSwitchAllRelatedInfo}.Ast.Extent.Text
                    )
        
                    $NewNatSB = {
                        # Load the functions we packed up:
                        $using:FunctionsForRemoteUse | foreach { Invoke-Expression $_ }
                        [System.Collections.ArrayList]$VMSettingsThatWereChangedInSB = @()
                        
                        if (!$using:SkipHyperVInstallCheck) {
                            # Install Hyper-V Features if they haven't aready

                            # NOTE: Below $HyperVFeaturesInstallResults contains properties 'InstallResults' (array of InstallFeatureDism
                            # pscustomobjects which contiain properties contains properties [string]Path, [bool]Online, [string]WinPath,
                            # [string]SysDrivePath, [bool]RestartNeeded, [string]$LogPath, [string]ScratchDirectory,
                            # [string]LogLevel), and 'InstallFailures' (array of strings of Dism Feature Names that
                            # failed to install).
                            # NOTE: InstallHyperVFeatures returns $null if everything is already installed.
                            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"
                                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"
                                return
                            }

                            if ($HyperVFeaturesInstallResults.InstallFailures.Count -gt 0) {
                                Write-Error "Please remedy the Hyper-V Features that failed to install before proceeding. Halting!"
                                $global:FunctionResult = "1"
                                return
                            }
                            
                            if ($HyperVFeaturesInstallResults.InstallResults.RestartNeeded -notcontains $True -and !$InstallContainersFeatureDismResult.RestartNeeded) {
                                Write-Host "All dependencies are already installed...proceeding..." -ForegroundColor Green
                            }
                            else {
                                if ($HyperVFeaturesInstallResults.InstallResults.RestartNeeded -contains $True -or $InstallContainersFeatureDismResult.RestartNeeded) {
                                    if ($using:AllowRestarts) {
                                        Write-Host "Shutting down $env:ComputerName..."
                                        # NOTE: The below output "Restarting" is important when running this function via Invoke-Command
                                        $null = $VMSettingsThatWereChangedInSB.Add("HyperVInstall")
                                        $null = $VMSettingsThatWereChangedInSB.Add("Restart")
                                        $VMSettingsThatWereChangedInSB
                                        Stop-Computer -Confirm:$false
                                        return
                                    }
                                    else {
                                        Write-Error "You must restart $env:ComputerName before proceeding! Halting!"
                                        $null = $VMSettingsThatWereChangedInSB.Add("HyperVInstall")
                                        $null = $VMSettingsThatWereChangedInSB.Add("RestartNeeded")
                                        $VMSettingsThatWereChangedInSB
                                        return
                                    }
                                }
                            }
                        }

                        try {
                            if ($(Get-Module -ListAvailable).Name -notcontains "Hyper-V" -and $(Get-Module).Name -notcontains "Hyper-V") {
                                throw "Hyper-V does NOT appear to be installed on $env:ComputerName! Halting!"
                            }
                            # NOTE: New-VMSwitch is the cmdlet that actually creates the Network Adapter called 'vEthernet ($NATName)'
                            # New-NetNat is another type of object that we just happen to be calling the same thing (i.e. '$NATName'),
                            # but it doesn't HAVE to be named the same thing.

                            # NOTE: 10.075.0/24 is the default Docker For Windows (i.e. Docker CE) NAT subnet. Figured we might as well
                            # use the same if one isn't provided to the is function.
                            if (!$using:NATIP) {
                                $NATIPSB = "10.0.75.1"
                            }
                            else {
                                $NATIPSB = $using:NATIP
                            }
                            if (!$using:NATNetworkMask) {
                                $NATNetworkMaskSB = 24
                            }
                            else {
                                $NATNetworkMaskSB = $using:NATNetworkMask
                            }
                            if (!$using:NATName) {
                                $NATNameSB = "LocalNAT"
                            }
                            else {
                                $NATNameSB = $using:NATName
                            }
                            $NATSubnet = "$NATIPSB/$NATNetworkMaskSB"

                            [System.Collections.ArrayList]$ExistingvSwitchInfo = @()
                            if ([bool]$(GetvSwitchAllRelatedInfo -IPAddress $NATIPSB -ErrorAction SilentlyContinue -WarningAction SilentlyContinue)) {
                                $vSwitchInfoByIP = GetvSwitchAllRelatedInfo -IPAddress $NATIPSB -ErrorAction SilentlyContinue -WarningAction SilentlyContinue

                                if ($vSwitchInfoByIP) {
                                    $null = $ExistingvSwitchInfo.Add($vSwitchInfoByIP)
                                }
                            }
                            if ([bool]$(GetvSwitchAllRelatedInfo -vSwitchName $NATNameSB -ErrorAction SilentlyContinue -WarningAction SilentlyContinue)) {
                                $vSwitchInfoByName = GetvSwitchAllRelatedInfo -vSwitchName $NATNameSB -ErrorAction SilentlyContinue -WarningAction SilentlyContinue

                                if ($vSwitchInfoByName) {
                                    $null = $ExistingvSwitchInfo.Add($vSwitchInfoByName)
                                }
                            }
                            
                            if ($ExistingvSwitchInfo.Count -eq 0) {
                                if ($PSVersionTable.PSEdition -eq "Core") {
                                    Invoke-WinCommand -ComputerName localhost -ScriptBlock {
                                        $null = New-NetNat -Name $args[0] -InternalIPInterfaceAddressPrefix $args[1]
                                        $null = New-VMSwitch -Name $args[0] -SwitchType Internal
                                        $null = Get-NetAdapter "vEthernet ($($args[0]))" | New-NetIPAddress -IPAddress $args[2] -AddressFamily IPv4 -PrefixLength $args[3]
                                    } -ArgumentList $NATNameSB,$NATSubnet,$NATIPSB,$NATNetworkMaskSB
                                }
                                else {
                                    $null = New-NetNat -Name $NATNameSB -InternalIPInterfaceAddressPrefix $NATSubnet
                                    $null = New-VMSwitch -Name $NATNameSB -SwitchType Internal
                                    $null = Get-NetAdapter "vEthernet ($NATNameSB)" | New-NetIPAddress -IPAddress $NATIPSB -AddressFamily IPv4 -PrefixLength $NATNetworkMaskSB
                                }
                                
                                $null = $VMSettingsThatWereChangedInSB.Add("NetworkAddressTranslation")
                            }
                            else {
                                $null = $VMSettingsThatWereChangedInSB.Add("None - LocalNAT Already Exists")
                            }

                            $VMSettingsThatWereChangedInSB
                        }
                        catch {
                            Write-Error $_
                            Write-Warning "Failed to create 'vEthernet ($NATNameSB)'! However, it is possible that Mac Address Spoofing is enabled on this Guest VM, in which case Nested VM networking will work as expected without NAT."
                        }
                    }

                    $InvCmdSplatParams = @{
                        ComputerName        = $GuestVMAndHVInfo.TargetHostInvCmdLocation
                        ScriptBlock         = $NewNatSB
                        ErrorAction         = "SilentlyContinue"
                        ErrorVariable       = "HVIErr"
                    }
                    if ($TargetHostNameCreds) {
                        $InvCmdSplatParams.Add("Credential",$TargetHostNameCreds)
                    }
            
                    try {
                        [array]$VMSettingsThatWereChangedPrep = Invoke-Command @InvCmdSplatParams
                        if (!$VMSettingsThatWereChangedPrep) {throw "The NewNATSB failed!"}

                        foreach ($Setting in $VMSettingsThatWereChangedPrep) {
                            $null = $VMSettingsThatWereChanged.Add($Setting)
                        }
                    }
                    catch {
                        if ($($HVIErr | Out-String) -notmatch "WinRM cannot complete the operation") {
                            Write-Error $_
                            $ErrMsgNewNATSB = "The EnableNestedVM function was unable to gather additional information " +
                            "about $($GuestVMAndHVInfo.TargetHostInvCmdLocation) by remoting into " +
                            "$($GuestVMAndHVInfo.TargetHostInvCmdLocation)! If a restart occurred, it might not be " +
                            "ready yet. Halting!"
                            Write-Error $ErrMsgNewNATSB
                            $global:FunctionResult = "1"
                            return
                        }
                        else {
                            $RestartOccurredFlag = $True
                            $null = $VMSettingsThatWereChanged.Add("HyperVInstall")
                            $null = $AttemptedChanges.Add("HyperVInstall")
                            $null = $NeededChanges.Add("HyperVInstall")
                        }
                    }
                }
            }

            if ($Locale -eq "Hypervisor") {
                if ($($VMInfo.State -eq 'Saved' -or $VMInfo.DynamicMemoryEnabled -eq $true -or
                $VMInfo.ExposeVirtualizationExtensions -eq $false -or $VMInfo.ProcessorCount -lt 2) -and
                $AllowRestarts
                ) {
                    if ($VMSettingsThatWereChanged -contains "HyperVInstall") {
                        Start-Sleep -Seconds 30
                    }
                    while ($(Get-VM $VMName).State -ne "Off") {
                        if ($(Get-VM $VMName).State -ne "Stopping") {
                            try {
                                Stop-VM -VMName $VMName -TurnOff -Confirm:$False -Force -WarningAction SilentlyContinue -WarningVariable StateCheck
                            }
                            catch {
                                Start-Sleep -Seconds 15
                            }
                        }
                    }

                    $i = 0
                    while ($(Get-VM -Name $VMName).State -ne "Off") {
                        Write-Verbose "Waiting for $VMName to turn off..."
                        $i++
                        if ($i -gt 10) {
                            Write-Error "$VMName hasn't turned off within 10 seconds! Please check its status manually on the hypervisor. Halting!"
                            $global:FunctionResult = "1"
                            return
                        }
                        Start-Sleep -Seconds 1
                    }

                    $null = $VMSettingsThatWereChangedInSB.Add("Restart")
                }
                if ($VMInfo.State -eq 'Saved' -and $AllowRestarts) {
                    Remove-VMSavedState -VMName $VMName
                    #Add-Member -InputObject $VMSettingsThatWereChanged NoteProperty -Name "SavedState" -Value "Removed"
                    $null = $VMSettingsThatWereChanged.Add("RemoveSavedState")
                }
                if ($VMInfo.DynamicMemoryEnabled -eq $true -and $AllowRestarts) {
                    Set-VMMemory -VMName $VMName -DynamicMemoryEnabled $false
                    #Add-Member -InputObject $VMSettingsThatWereChanged NoteProperty -Name "DynamicMemory" -Value "Disabled"
                    $null = $VMSettingsThatWereChanged.Add("DisableDynamicMemory")
                }
                if ($VMInfo.ExposeVirtualizationExtensions -eq $false -and $AllowRestarts) {
                    Set-VMProcessor -VMName $VMName -ExposeVirtualizationExtensions $true
                    #Add-Member -InputObject $VMSettingsThatWereChanged NoteProperty -Name "ExposeVirtualizationExtensions" -Value $true
                    $null = $VMSettingsThatWereChanged.Add("ExposeVirtualizationExtensions")
                }
                if ($VMInfo.MacAddressSpoofing -eq 'Off' -and !$NoMacAddressSpoofing) {
                    Set-VMNetworkAdapter -VMName $VMName -MacAddressSpoofing on
                    #Add-Member -InputObject $VMSettingsThatWereChanged NoteProperty -Name "MacAddressSpoofing" -Value "On"
                    $null = $VMSettingsThatWereChanged.Add("TurnMacSpoofingOn")
                }
                if ($VMInfo.MemorySize -ne $FinalMem) {
                    Set-VMMemory -VMName $VMName -StartupBytes $FinalMem
                    #Add-Member -InputObject $VMSettingsThatWereChanged NoteProperty -Name "VMMemory" -Value $FinalMem
                    $null = $VMSettingsThatWereChanged.Add("AdjustStartupMemory")
                }
                if ($VMInfo.ProcessorCount -lt 2) {
                    Set-VMProcessor -VMname $VMName -Count 2
                    #Add-Member -InputObject $VMSettingsThatWereChanged NoteProperty -Name "UpProcessorCount" -Value 2
                    $null = $VMSettingsThatWereChanged.Add("UpProcessorCount")
                }

                if ($(Get-VM -Name $VMName).State -eq "Off") {
                    Start-VM -VMName $VMName

                    $i = 0
                    while ($(Get-VM -Name $VMName).State -ne "Running") {
                        Write-Verbose "Waiting for $VMName to turn on..."
                        $i++
                        if ($i -gt 10) {
                            Write-Error "$VMName hasn't turned on within 10 seconds! Please check its status manually on the hypervisor. Halting!"
                            $global:FunctionResult = "1"
                            return
                        }
                        Start-Sleep -Seconds 1
                    }
                }
            }

            if ($Locale -match "GuestVM|Elsewhere" -and
            $($GuestVMAndHVInfo.VirtualizationExtensionsExposed -eq $False -or
            $VMInfo.State -eq 'Saved' -or $VMInfo.DynamicMemoryEnabled -eq $true -or
            $VMInfo.ExposeVirtualizationExtensions -eq $false -or
            $($VMInfo.MacAddressSpoofing -eq 'Off' -and !$NoMacAddressSpoofing) -or
            $VMInfo.MemorySize -ne $FinalMem)
            ) {
                $HypervisorGuestVMChangesSB = {
                    [System.Collections.ArrayList]$VMSettingsThatWereChangedInSB = @()
                    
                    if ($($using:VMInfo.State -eq 'Saved' -or $using:VMInfo.DynamicMemoryEnabled -eq $true -or
                    $using:VMInfo.ExposeVirtualizationExtensions -eq $false -or $using:VMInfo.ProcessorCount -lt 2) -and
                    $using:AllowRestarts
                    ) {
                        if ($using:VMSettingsThatWereChanged -contains "HyperVInstall") {
                            Start-Sleep -Seconds 30
                        }
                        while ($(Get-VM $using:VMName).State -ne "Off") {
                            if ($(Get-VM $using:VMName).State -ne "Stopping") {
                                try {
                                    Stop-VM -VMName $using:VMName -TurnOff -Confirm:$False -Force -WarningAction SilentlyContinue -WarningVariable StateCheck
                                }
                                catch {
                                    Start-Sleep -Seconds 15
                                }
                            }
                        }

                        $i = 0
                        while ($(Get-VM -Name $using:VMName).State -ne "Off") {
                            Write-Verbose "Waiting for $($using:VMName) to turn off..."
                            $i++
                            if ($i -gt 10) {
                                Write-Error "$($using:VMName) hasn't turned off within 10 seconds! Please check its status manually on the hypervisor. Halting!"
                                $global:FunctionResult = "1"
                                return
                            }
                            Start-Sleep -Seconds 1
                        }

                        $null = $VMSettingsThatWereChangedInSB.Add("Restart")
                    }
                    if ($using:VMInfo.State -eq 'Saved' -and $using:AllowRestarts) {
                        Remove-VMSavedState -VMName $using:VMName
                        #Add-Member -InputObject $VMSettingsThatWereChangedInSB NoteProperty -Name "SavedState" -Value "Removed"
                        $null = $VMSettingsThatWereChangedInSB.Add("RemoveSavedState")
                    }
                    if ($using:VMInfo.DynamicMemoryEnabled -eq $true -and $using:AllowRestarts) {
                        Set-VMMemory -VMName $using:VMName -DynamicMemoryEnabled $false
                        #Add-Member -InputObject $VMSettingsThatWereChangedInSB NoteProperty -Name "DynamicMemory" -Value "Disabled"
                        $null = $VMSettingsThatWereChangedInSB.Add("DisableDynamicMemory")
                    }
                    if ($using:VMInfo.ExposeVirtualizationExtensions -eq $false -and $using:AllowRestarts) {
                        Set-VMProcessor -VMName $using:VMName -ExposeVirtualizationExtensions $true
                        #Add-Member -InputObject $VMSettingsThatWereChangedInSB NoteProperty -Name "ExposeVirtualizationExtensions" -Value $true
                        $null = $VMSettingsThatWereChangedInSB.Add("ExposeVirtualizationExtensions")
                    }
                    if ($using:VMInfo.MacAddressSpoofing -eq 'Off') {
                        Set-VMNetworkAdapter -VMName $using:VMName -MacAddressSpoofing On
                        #Add-Member -InputObject $VMSettingsThatWereChangedInSB NoteProperty -Name "MacAddressSpoofing" -Value "On"
                        $null = $VMSettingsThatWereChangedInSB.Add("TurnMacSpoofingOn")
                    }
                    if ($using:VMInfo.MemorySize -ne $using:FinalMem) {
                        Set-VMMemory -VMName $using:VMName -StartupBytes $using:FinalMem
                        #Add-Member -InputObject $VMSettingsThatWereChangedInSB NoteProperty -Name "VMMemory" -Value $using:FinalMem
                        $null = $VMSettingsThatWereChangedInSB.Add("AdjustStartupMemory")
                    }
                    if ($using:VMInfo.ProcessorCount -lt 2) {
                        Set-VMProcessor -VMname $using:VMName -Count 2
                        #Add-Member -InputObject $VMSettingsThatWereChanged NoteProperty -Name "UpProcessorCount" -Value 2
                        $null = $VMSettingsThatWereChangedInSB.Add("UpProcessorCount")
                    }
        
                    if ($(Get-VM -Name $using:VMName).State -eq "Off") {
                        Start-VM -VMName $using:VMName
        
                        $i = 0
                        while ($(Get-VM -Name $using:VMName).State -ne "Running") {
                            Write-Verbose "Waiting for $($using:VMName) to turn on..."
                            $i++
                            if ($i -gt 10) {
                                Write-Error "$($using:VMName) hasn't turned on within 10 seconds! Please check its status manually on the hypervisor. Halting!"
                                $global:FunctionResult = "1"
                                return
                            }
                            Start-Sleep -Seconds 1
                        }
                    }
        
                    $VMSettingsThatWereChangedInSB
                }

                $InvCmdSplatParams = @{
                    ComputerName        = $GuestVMAndHVInfo.HypervisorInvCmdLocation
                    ScriptBlock         = $HypervisorGuestVMChangesSB
                    ErrorAction         = "Stop"
                }
                if ($HypervisorCreds) {
                    $InvCmdSplatParams.Add("Credential",$HypervisorCreds)
                }
        
                try {
                    #$GuestVMAndHVInfo | Export-Clixml $HOME\Downloads\GuestVMAndHVInfo.xml
                    #$InvCmdSplatParams | Export-Clixml $HOME\Downloads\InvCmdSplatParams.xml
                    $VMSettingsThatWereChangedPrep = Invoke-Command @InvCmdSplatParams
                    if (!$VMSettingsThatWereChangedPrep) {throw "The NewNATSB failed!"}

                    foreach ($Setting in $VMSettingsThatWereChangedPrep) {
                        if ($VMSettingsThatWereChanged -notcontains $Setting) {
                            $null = $VMSettingsThatWereChanged.Add($Setting)
                        }   
                    }
                }
                catch {
                    Write-Error $_
                    Write-Error "The EnableNestedVM function was unable to report on `$VMSettingsThatWereChanged by remoting into the Hyper-V Host! Halting!"
                    $global:FunctionResult = "1"
                    return
                }
            }
        }

        if($char -match "No|no|N|n") {
            Write-Error "User chose not to proceed! No action taken! Halting!"
            $global:FunctionResult = "0"
            return
        }
    }
    if ($TryWithoutHypervisorInfo) {
        if ($TryWithoutHypervisorInfo -and !$NoMacAddressSpoofing) {
            $WarnMsg = "We are attempting to configure the Guest VM for Nested Virtualization without any information (or access to) the Hypervisor. " +
            "This just means that the only configuration action we might take is attempting to create a NAT interface on the Guest VM."
            Write-Warning $WarnMsg
        }

        if ($GuestVMNestedVirtCapabilties.NetworkingPossibilities -contains "Mac Address Spoofing" -and !$NoMacAddressSpoofing) {
            Write-Warning "Mac Address Spoofing is enabled. We will NOT create a NAT interface on the Guest VM. No action taken."

            [System.Collections.ArrayList]$VMSettingsThatWereChanged = @("None")
            [System.Collections.ArrayList]$NeededChanges = @("None")
            [System.Collections.ArrayList]$AttemptedChanges = @("None")
            [System.Collections.ArrayList]$UnsatisfiedChanges = @("None")
        }
        else {
            [System.Collections.ArrayList]$NeededChanges = @("NetworkAddressTranslation")
            $AttemptedChanges = $NeededChanges

            Write-Warning "Potential changes to $($GuestVMAndHVInfo.HostNameNetworkInfo.HostName) are as follows:"
            $WarnMsg1 = "If a restart IS NOT necessary (i.e. if Hyper-V is already installed on $env:ComputerName), " +
            "and if an Internal vSwitch called 'LocalNAT' does NOT already exist and if NO OTHER Network Adapter " +
            "is using subnet $NATSubnet with IP $NATIP, then an Internal vSwitch called 'LocalNAT' will be " +
            "created with the aforementioned information"
            Write-Warning $WarnMsg1
            $WarnMsg2 = "If a restart IS necessary, please run the EnableNestedVM function again after the restart " +
            "once $env:Computer has finished installing Hyper-V components."
            Write-Warning $WarnMsg2
            Write-Host ""

            $prompt = $true

            if ($prompt) {
                if (!$SkipPrompt) {
                    Write-Host ""
                    $char = Read-Host -Prompt "Do you agree to all of these (potential) changes to VM $env:ComputerName ? [Yes/No]"
                    while ($char -notmatch "Yes|yes|Y|y|No|no|N|n") {
                        Write-Host "Invalid Input, Y or N"
                        $char = Read-Host -Prompt "Do you agree to all of these (potential) changes to VM $env:ComputerName? [Yes/No]"
                    }
                }
                else {
                    $char = "Yes"
                }
            }

            if($char -match "No|no|N|n") {
                Write-Error "User chose not to proceed! No action taken! Halting!"
                $global:FunctionResult = "0"
                return
            }

            if ($char -match "Yes|yes|Y|y") {
                if ($Locale -eq "GuestVM") {
                    [System.Collections.ArrayList]$VMSettingsThatWereChanged = @()
                    
                    if (!$SkipHyperVInstallCheck) {
                        # Install Hyper-V Features if they haven't aready

                        # NOTE: Below $HyperVFeaturesInstallResults contains properties 'InstallResults' (array of InstallFeatureDism
                        # pscustomobjects which contiain properties contains properties [string]Path, [bool]Online, [string]WinPath,
                        # [string]SysDrivePath, [bool]RestartNeeded, [string]$LogPath, [string]ScratchDirectory,
                        # [string]LogLevel), and 'InstallFailures' (array of strings of Dism Feature Names that
                        # failed to install).
                        # NOTE: InstallHyperVFeatures returns $null if everything is already installed.
                        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"
                            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"
                            return
                        }

                        if ($HyperVFeaturesInstallResults.InstallFailures.Count -gt 0) {
                            Write-Error "Please remedy the Hyper-V Features that failed to install before proceeding. Halting!"
                            $global:FunctionResult = "1"
                            return
                        }
                        
                        if ($HyperVFeaturesInstallResults.InstallResults.RestartNeeded -notcontains $True -and !$InstallContainersFeatureDismResult.RestartNeeded) {
                            Write-Host "All dependencies are already installed...proceeding..." -ForegroundColor Green
                        }
                        else {
                            if ($HyperVFeaturesInstallResults.InstallResults.RestartNeeded -contains $True -or $InstallContainersFeatureDismResult.RestartNeeded) {
                                if ($AllowRestarts) {
                                    Write-Host "Restarting $env:ComputerName..."
                                    # NOTE: The below output "Restarting" is important when running this function via Invoke-Command
                                    $null = $VMSettingsThatWereChanged.Add("HyperVInstall")
                                    $null = $VMSettingsThatWereChanged.Add("Restart")
                                    Restart-Computer -Confirm:$false -Force
                                }
                                else {
                                    Write-Error "You must restart $env:ComputerName before proceeding! Halting!"
                                    return
                                }
                            }
                        }
                    }

                    try {
                        if ($(Get-Module -ListAvailable).Name -notcontains "Hyper-V" -and $(Get-Module).Name -notcontains "Hyper-V") {
                            throw "Hyper-V does NOT appear to be installed on $env:ComputerName! Halting!"
                        }
                        # NOTE: New-VMSwitch is the cmdlet that actually creates the Network Adapter called 'vEthernet ($NATName)'
                        # New-NetNat is another type of object that we just happen to be calling the same thing (i.e. '$NATName'),
                        # but it doesn't HAVE to be named the same thing.

                        # NOTE: 10.075.0/24 is the default Docker For Windows (i.e. Docker CE) NAT subnet. Figured we might as well
                        # use the same if one isn't provided to the is function.
                        if (!$NATIP) {
                            $NATIP = "10.0.75.1"
                        }
                        if (!$NATNetworkMask) {
                            $NATNetworkMask = 24
                        }
                        if (!$NATName) {
                            $NATName = "LocalNAT"
                        }
                        $NATSubnet = "$NATIP/$NATNetworkMask"

                        [System.Collections.ArrayList]$ExistingvSwitchInfo = @()
                        if ([bool]$(GetvSwitchAllRelatedInfo -IPAddress $NATIP -ErrorAction SilentlyContinue -WarningAction SilentlyContinue)) {
                            $vSwitchInfoByIP = GetvSwitchAllRelatedInfo -IPAddress $NATIP -ErrorAction SilentlyContinue -WarningAction SilentlyContinue

                            if ($vSwitchInfoByIP) {
                                $null = $ExistingvSwitchInfo.Add($vSwitchInfoByIP)
                            }
                        }
                        if ([bool]$(GetvSwitchAllRelatedInfo -vSwitchName $NATName -ErrorAction SilentlyContinue -WarningAction SilentlyContinue)) {
                            $vSwitchInfoByName = GetvSwitchAllRelatedInfo -vSwitchName $NATName -ErrorAction SilentlyContinue -WarningAction SilentlyContinue

                            if ($vSwitchInfoByName) {
                                $null = $ExistingvSwitchInfo.Add($vSwitchInfoByName)
                            }
                        }

                        if ($ExistingvSwitchInfo.Count -eq 0) {
                            if ($PSVersionTable.PSEdition -eq "Core") {
                                Invoke-WinCommand -ComputerName localhost -ScriptBlock {
                                    $null = New-NetNat -Name $args[0] -InternalIPInterfaceAddressPrefix $args[1]
                                    $null = New-VMSwitch -Name $args[0] -SwitchType Internal
                                    $null = Get-NetAdapter "vEthernet ($($args[0]))" | New-NetIPAddress -IPAddress $args[2] -AddressFamily IPv4 -PrefixLength $args[3]
                                } -ArgumentList $NATName,$NATSubnet,$NATIP,$NATNetworkMask
                            }
                            else {
                                $null = New-NetNat -Name $NATName -InternalIPInterfaceAddressPrefix $NATSubnet
                                $null = New-VMSwitch -Name $NATName -SwitchType Internal
                                $null = Get-NetAdapter "vEthernet ($NATName)" | New-NetIPAddress -IPAddress $NATIP -AddressFamily IPv4 -PrefixLength $NATNetworkMask
                            }
                        
                            [System.Collections.ArrayList]$NeededChanges = @("NetworkAddressTranslation")
                        }
                        else {
                            [System.Collections.ArrayList][Array]$NeededChanges = @("None - LocalNAT Already Exists")
                        }
                        
                        $AttemptedChanges = $NeededChanges
                        $VMSettingsThatWereChanged = $NeededChanges
                    }
                    catch {
                        Write-Error $_
                        Write-Warning "Failed to create 'vEthernet ($NATName)'! However, it is possible that Mac Address Spoofing is enabled on this Guest VM, in which case"
                        $AttemptedChanges = $NeededChanges
                        $UnsatisfiedChanges = $NeededChanges
                    }
                }
                elseif ($Locale -eq "Elsewhere") {
                    $FunctionsForRemoteUse = @(
                        ${Function:InstallFeatureDism}.Ast.Extent.Text
                        ${Function:InstallHyperVFeatures}.Ast.Extent.Text
                        ${Function:TestIsValidIPAddress}.Ast.Extent.Text
                        ${Function:GetvSwitchAllRelatedInfo}.Ast.Extent.Text
                    )
        
                    $NewNatSB = {
                        # Load the functions we packed up:
                        $using:FunctionsForRemoteUse | foreach { Invoke-Expression $_ }
                        [System.Collections.ArrayList]$VMSettingsThatWereChangedInSB = @()
                        
                        if (!$using:SkipHyperVInstallCheck) {
                            # Install Hyper-V Features if they haven't aready

                            # NOTE: Below $HyperVFeaturesInstallResults contains properties 'InstallResults' (array of InstallFeatureDism
                            # pscustomobjects which contiain properties contains properties [string]Path, [bool]Online, [string]WinPath,
                            # [string]SysDrivePath, [bool]RestartNeeded, [string]$LogPath, [string]ScratchDirectory,
                            # [string]LogLevel), and 'InstallFailures' (array of strings of Dism Feature Names that
                            # failed to install).
                            # NOTE: InstallHyperVFeatures returns $null if everything is already installed.
                            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"
                                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"
                                return
                            }

                            if ($HyperVFeaturesInstallResults.InstallFailures.Count -gt 0) {
                                Write-Error "Please remedy the Hyper-V Features that failed to install before proceeding. Halting!"
                                $global:FunctionResult = "1"
                                return
                            }
                            
                            if ($HyperVFeaturesInstallResults.InstallResults.RestartNeeded -notcontains $True -and !$InstallContainersFeatureDismResult.RestartNeeded) {
                                Write-Host "All dependencies are already installed...proceeding..." -ForegroundColor Green
                            }
                            else {
                                if ($HyperVFeaturesInstallResults.InstallResults.RestartNeeded -contains $True -or $InstallContainersFeatureDismResult.RestartNeeded) {
                                    if ($using:AllowRestarts) {
                                        Write-Host "Restarting $env:ComputerName..."
                                        # NOTE: The below output "Restarting" is important when running this function via Invoke-Command
                                        $null = $VMSettingsThatWereChangedInSB.Add("HyperVInstall")
                                        $null = $VMSettingsThatWereChangedInSB.Add("Restart")
                                        $VMSettingsThatWereChangedInSB
                                        Restart-Computer -Confirm:$false -Force
                                        return
                                    }
                                    else {
                                        Write-Error "You must restart $env:ComputerName before proceeding! Halting!"
                                        $null = $VMSettingsThatWereChangedInSB.Add("HyperVInstall")
                                        $null = $VMSettingsThatWereChangedInSB.Add("RestartNeeded")
                                        $VMSettingsThatWereChangedInSB
                                        return
                                    }
                                }
                            }
                        }

                        try {
                            if ($(Get-Module -ListAvailable).Name -notcontains "Hyper-V" -and $(Get-Module).Name -notcontains "Hyper-V") {
                                throw "Hyper-V does NOT appear to be installed on $env:ComputerName! Halting!"
                            }
                            # NOTE: New-VMSwitch is the cmdlet that actually creates the Network Adapter called 'vEthernet ($NATName)'
                            # New-NetNat is another type of object that we just happen to be calling the same thing (i.e. '$NATName'),
                            # but it doesn't HAVE to be named the same thing.

                            # NOTE: 10.075.0/24 is the default Docker For Windows (i.e. Docker CE) NAT subnet. Figured we might as well
                            # use the same if one isn't provided to the is function.
                            if (!$using:NATIP) {
                                $NATIPSB = "10.0.75.1"
                            }
                            else {
                                $NATIPSB = $using:NATIP
                            }
                            if (!$using:NATNetworkMask) {
                                $NATNetworkMaskSB = 24
                            }
                            else {
                                $NATNetworkMaskSB = $using:NATNetworkMask
                            }
                            if (!$using:NATName) {
                                $NATNameSB = "LocalNAT"
                            }
                            else {
                                $NATNameSB = $using:NATName
                            }
                            $NATSubnet = "$NATIPSB/$NATNetworkMaskSB"

                            [System.Collections.ArrayList]$ExistingvSwitchInfo = @()
                            if ([bool]$(GetvSwitchAllRelatedInfo -IPAddress $NATIPSB -ErrorAction SilentlyContinue -WarningAction SilentlyContinue)) {
                                $vSwitchInfoByIP = GetvSwitchAllRelatedInfo -IPAddress $NATIPSB -ErrorAction SilentlyContinue -WarningAction SilentlyContinue

                                if ($vSwitchInfoByIP) {
                                    $null = $ExistingvSwitchInfo.Add($vSwitchInfoByIP)
                                }
                            }
                            if ([bool]$(GetvSwitchAllRelatedInfo -vSwitchName $NATNameSB -ErrorAction SilentlyContinue -WarningAction SilentlyContinue)) {
                                $vSwitchInfoByName = GetvSwitchAllRelatedInfo -vSwitchName $NATNameSB -ErrorAction SilentlyContinue -WarningAction SilentlyContinue

                                if ($vSwitchInfoByName) {
                                    $null = $ExistingvSwitchInfo.Add($vSwitchInfoByName)
                                }
                            }
                            
                            if ($ExistingvSwitchInfo.Count -eq 0) {
                                if ($PSVersionTable.PSEdition -eq "Core") {
                                    Invoke-WinCommand -ComputerName localhost -ScriptBlock {
                                        $null = New-NetNat -Name $args[0] -InternalIPInterfaceAddressPrefix $args[1]
                                        $null = New-VMSwitch -Name $args[0] -SwitchType Internal
                                        $null = Get-NetAdapter "vEthernet ($($args[0]))" | New-NetIPAddress -IPAddress $args[2] -AddressFamily IPv4 -PrefixLength $args[3]
                                    } -ArgumentList $NATNameSB,$NATSubnet,$NATIPSB,$NATNetworkMaskSB
                                }
                                else {
                                    $null = New-NetNat -Name $NATNameSB -InternalIPInterfaceAddressPrefix $NATSubnet
                                    $null = New-VMSwitch -Name $NATNameSB -SwitchType Internal
                                    $null = Get-NetAdapter "vEthernet ($NATNameSB)" | New-NetIPAddress -IPAddress $NATIPSB -AddressFamily IPv4 -PrefixLength $NATNetworkMaskSB
                                }
                            
                                $null = $VMSettingsThatWereChangedInSB.Add("NetworkAddressTranslation")
                            }
                            else {
                                $null = $VMSettingsThatWereChangedInSB.Add("None - LocalNAT Already Exists")
                            }

                            $VMSettingsThatWereChangedInSB
                        }
                        catch {
                            Write-Error $_
                            Write-Warning "Failed to create 'vEthernet ($NATNameSB)'! However, it is possible that Mac Address Spoofing is enabled on this Guest VM, in which case Nested VM networking will work as expected without NAT."
                        }
                    }

                    $InvCmdSplatParams = @{
                        ComputerName        = $GuestVMAndHVInfo.TargetHostInvCmdLocation
                        ScriptBlock         = $NewNatSB
                        ErrorAction         = "SilentlyContinue"
                        ErrorVariable       = "HVIErr"
                    }
                    if ($TargetHostNameCreds) {
                        $InvCmdSplatParams.Add("Credential",$TargetHostNameCreds)
                    }
            
                    try {
                        $VMSettingsThatWereChanged = Invoke-Command @InvCmdSplatParams
                        if (!$VMSettingsThatWereChanged) {throw "The NewNATSB failed!"}

                        if ($VMSettingsThatWereChanged -contains "None - LocalNAT Already Exists") {
                            [System.Collections.ArrayList]$NeededChanges = @("None - LocalNAT Already Exists")
                        }
                        $AttemptedChanges = $NeededChanges
                        if ($VMSettingsThatWereChanged -eq $null) {
                            $UnsatisfiedChanges = $NeededChanges
                        }
                    }
                    catch {
                        if ($($HVIErr | Out-String) -notmatch "WinRM cannot complete the operation") {
                            Write-Error $_
                            $ErrMsgNewNATSB = "The EnableNestedVM function was unable to gather additional information " +
                            "about $($GuestVMAndHVInfo.TargetHostInvCmdLocation) by remoting into " +
                            "$($GuestVMAndHVInfo.TargetHostInvCmdLocation)! If a restart occurred, it might not be " +
                            "ready yet. Halting!"
                            Write-Error $ErrMsgNewNATSB
                            $global:FunctionResult = "1"
                            return
                        }
                        else {
                            $RestartOccurred = $True
                            [System.Collections.ArrayList]$VMSettingsThatWereChanged = @("HyperVInstall")

                            [System.Collections.ArrayList]$AttemptedChanges = @("HyperVInstall")
                            $NeededChanges = $AttemptedChanges
                        }
                    }
                }
            }
        }
    }

    $RestartOccurred = if ($VMSettingsThatWereChanged -contains "Restart" -or $RestartOccurredFlag) {$True} else {$False}
    $RestartStillNeeded = if ($VMSettingsThatWereChanged -contains "RestartNeeded") {$True} else {$False}
    $ReRunFunction = if ($RestartOccurred -or $RestartStillNeeded) {$True} else {$False}

    foreach ($Setting in $VMSettingsThatWereChanged) {
        if ($NeededChanges -notcontains $Setting) {
            $null = $NeededChanges.Add($Setting)
        }
        if ($AttemptedChanges -notcontains $Setting) {
            $null = $AttemptedChanges.Add($Setting)
        }
    }

    if ($VMSettingsThatWereChanged.Count -eq 0) {
        Write-Warning "No changes were made to the Guest VM!"
    }
    if ($VMSettingsThatWereChanged.Count -eq 0 -and $AttemptedChanges.Count -gt 0) {
        Write-Warning "Changes to the Guest VM were attempted, but did not succeed! No changes were made to the Guest VM!"
    }
    if ($UnsatisfiedChanges.Count -gt 0) {
        Write-Warning "There are changes that still need to be made to the Guest VM in order to allow for a 64-bit Nested VM!"
    }
    if ($NeededChanges.Count -eq 0) {
        Write-Host "The VM $VMName is already configured for Nested Virtualization! No action taken!" -ForegroundColor green
    }

    [pscustomobject]@{
        GuestVMSettingsThatWereChanged       = [System.Collections.ArrayList][Array]$VMSettingsThatWereChanged
        NeededChanges                        = $NeededChanges
        AttemptedChanges                     = $AttemptedChanges
        UnsatisfiedChanges                   = $UnsatisfiedChanges
        RestartOccurred                      = $RestartOccurred
        RestartStillNeeded                   = $RestartStillNeeded
        ReRunFunction                        = $ReRunFunction
    }

    ##### END Main Body #####

}