MSFT.NetworkATC.CleanupScript.psm1

Function Remove-NetworkATCIntentSetUp {
Write-Warning -WarningAction Inquire -Message "This script will remove the following configuration from all servers in the local cluster:
  
- Intents created by Network ATC
- Virtual switch and host vNIC configurations created by Network ATC
- All Data Center Bridging policies
- All LBFO teams. LBFO is replaced by SET on the Azure Stack HCI OS. For more information, please see: https://docs.microsoft.com/en-us/azure-stack/hci/concepts/host-network-requirements#set
  
This script will NOT:
  
- Remove locally-deployed intents
- Reset adapter properties
- Destroy the cluster
- Destroy virtual switches not created by Network ATC
- Destroy virtual switches with virtual machine NICs (vmNICs) attached
 
During cleanup, it is possible that the cluster nodes, or the cluster IP resource temporarily lose network connectivity. Connectivity is expected to be restored in less than one minute. If connectivity to one of the servers is not restored, please connect to the system using a BMC. If the cluster IP resource is offline, re-enable it using Windows Admin Center."


<#
The script can't run if the cluster service isn't running, so flag this if we can't get the cluster name.
If we can get the cluster name, display the status of each node and exit if any nodes are offline.
#>

try {
    $clusname = Get-Cluster -ErrorAction Stop
}catch {
    Write-Host "The cluster service is not running. Make sure that the service is running on all nodes of the cluster." -ForegroundColor Red
    Exit
}

$clusternodes = Get-ClusterNode
$nodecount = 0
foreach ($clusternode in $clusternodes) {
    $node = Get-ClusterNode -Name "$clusternode" | Select-Object "State"
    if ($node.state -Like "Up") {
        Write-Host "Cluster node $clusternode is in state Up." -ForegroundColor Green
    }else {
        Write-Host "Cluster node $clusternode is in state Down." -ForegroundColor Red
        $nodecount++
    }
}

if($nodecount -gt 0) {
    Write-Warning -Message "$nodecount node(s) are in a down state. Bring these nodes online manually before continuing."
    Exit
}

<#
Check for local intents on each node.
If local intents are found, display the nodes where the local intents were detected and increment a counter.
If the counter >0, display the count of the number of nodes where local intents were detected and exit.
#>

$intentcount = 0
foreach ($clusternode in $clusternodes) {
    $localintents = Get-NetIntent -ComputerName $clusternode
    if ($localintents) {
        Write-Warning -Message "Local intents have been detected on node $clusternode."
        $intentcount++
    }else {
        Write-Host "No local intents found on node $clusternode." -ForegroundColor Green
    }
}

if($intentcount -gt 0) {
    Write-Warning -Message "Local intents were found on $intentcount node(s). Manually remove these local intents before continuing."
    Exit
}

<#
If no local intents, detect the cluster name and if any IPs are online and output this to the user.
If the cluster name is not online, the script will fail at attempting to get the intents.
If cluster intents are not detected, the script will warn the user.
If cluster intents are detected, the script will remove the intents.
#>

try {
    $intents = Get-NetIntent -ClusterName $clusname -ErrorAction Stop
}catch {
    $clustername = Get-ClusterResource | Where-Object ResourceType -Like "Network Name" | Where-Object State -Like "Offline" | Get-ClusterParameter | Where-Object Name -Like "Name" 
    if ($clustername) {
        Write-Host "The Cluster Network Name Object (CNO)"$clustername.value"is offline. Online the resource and rerun the script." -ForegroundColor Red
        Exit
    }else {
        Write-Host "Failed to retrieve intent information from the cluster." -ForegroundColor Red
        Exit
    }
}

$clustername = Get-ClusterResource | Where-Object ResourceType -Like "Network Name" | Where-Object State -Like "Online" | Get-ClusterParameter | Where-Object Name -Like "Name" 
$clusterip = Get-ClusterResource | Where-Object ResourceType -Like "IP Address" | Where-Object State -Like "Online" | Get-ClusterParameter | Where-Object Name -Like "Address"

Write-Host "Cluster name"$clustername.value"is online." -ForegroundColor Green
    
if ($clusterip) {
    foreach ($ip in $clusterip) {
        Write-Host "Cluster IP"$clusterip.value"is online." -ForegroundColor Green
    }
}else {
    Write-Host "No cluster IP is online" -ForegroundColor Yellow
}

if ($intents) {
    Write-Host "Cluster intents detected. These intents will now be removed." -ForegroundColor Green
    foreach ($intent in $intents) {
        Write-Host "Attempting to remove intent"$intent.IntentName"."
        Remove-NetIntent -Name $intent.IntentName -ClusterName $clusname
        $intentafter = Get-NetIntent -Name $intent.IntentName -ClusterName $clusname
        if (!$intentafter) {
            Write-Host "Intent"$intent.IntentName"was successfully removed." -ForegroundColor Green
        }else {
            Write-Host "Intent"$intent.IntentName"was not removed." -ForegroundColor Red
        }
    }
}else {
    Write-Warning "No cluster intents were detected."
}

<#
Detect virtual switches created by Network ATC using the common naming convension.
If virtual switches are detected, report the name and node where the virtual switch was detected.
Then attempt to remove the virtual switch and report the result.
Virtual switches that have VMs attached will not be able to be removed.
But in the future, support could be added to attempt to disconnect these VMs using something similar to below:
Get-VM | Get-VMNetworkAdapter | where SwitchName -Like "ConvergedSwitch($($intent.IntentName))" | Disconnect-VMNetworkAdapter
#>

foreach ($intent in $intents)
{
    foreach ($clusternode in $clusternodes)
    {
        $switchbefore = Get-VMSwitch -Name "ConvergedSwitch($($intent.IntentName))" -ComputerName $clusternode -ErrorAction SilentlyContinue
        if ($switchbefore) {
            Write-Host "Virtual switch"$switchbefore.Name"detected on host $clusternode. Attempting to remove this virtual switch." -ForegroundColor Green
            try {
                Remove-VMSwitch -Name "ConvergedSwitch($($intent.IntentName))" -ComputerName $clusternode -ErrorAction Stop -Force
                Start-Sleep 30
            }catch {
                Write-Host "Virtual switch"$switchbefore.Name"could not be removed from host $clusternode. This switch may have virtual machines attached." -ForegroundColor Yellow
                try {
                    Write-Warning -WarningAction Inquire -Message "Allow the script to attempt to disconnct these virtual machines?" -ErrorAction Stop
                    Get-VM | Get-VMNetworkAdapter | Where-Object "SwitchName" -eq "ConvergedSwitch($($intent.IntentName))" | Disconnect-VMNetworkAdapter -ErrorAction Stop
                    Remove-VMSwitch -Name "ConvergedSwitch($($intent.IntentName))" -ComputerName $clusternode -ErrorAction Stop -Force
                    Start-Sleep 30
                }catch {
                    Write-Host "Virtual switch"$switchbefore.Name"could not be removed from host $clusternode. This switch may have virtual machines attached and will need to be manually removed from the host." -ForegroundColor Red
                }
            }finally {
                $switchafter = Get-VMSwitch -Name "ConvergedSwitch($($intent.IntentName))" -ComputerName $clusternode -ErrorAction SilentlyContinue
                if (!$switchafter) {
                    Write-Host "Virtual switch"$switchbefore.Name"was successfully removed from host $clusternode." -ForegroundColor Green
                }
            }
        }else {
            Write-Host "No virtual switch detected on node $clusternode." -ForegroundColor Yellow
        }
    }
}

<#
Remove QoS configurations from each cluster node.
Then detect for and remove any unsupported LBFO teams that may be present on any cluster nodes.
These are grouped to take advantage of using a single cimsession and not needing to recreate any.
#>

foreach ($clusternode in $clusternodes) {
    $CimSession = New-CimSession -ComputerName $clusternode -Name $clusternode
    Get-NetQosTrafficClass -CimSession $CimSession | Remove-NetQosTrafficClass -CimSession $CimSession
    Get-NetQosPolicy -CimSession $CimSession | Remove-NetQosPolicy -Confirm:$false -CimSession $CimSession
    Get-NetQosFlowControl -CimSession $CimSession | Disable-NetQosFlowControl -CimSession $CimSession
 
    $LBFOBefore = Get-NetLbfoTeam -CimSession $CimSession
    if ($LBFOBefore) {
        Write-Warning -Message "LBFO teams have been detected on cluster node $clusternode. LBFO has been replaced by SET. For more information, please see: https://docs.microsoft.com/en-us/azure-stack/hci/concepts/host-network-requirements#set. These LBFO teams will now be removed."
        Get-NetLbfoTeam -CimSession $CimSession | Remove-NetLbfoTeam -Confirm:$false
        $LBFOAfter = Get-NetLbfoTeam -CimSession $CimSession
        if ($LBFOBefore -eq $LBFOAfter) {
            Write-Host "LBFO team"$LBFOBefore.Name"could not be removed from host $clusternode. This team will need to be manually removed." -ForegroundColor Red
        }else {
            Write-Host "LBFO team"$LBFOBefore.Name"was successfully removed from host $clusternode" -ForegroundColor Green
        }
    }
    Get-CimSession | Remove-CimSession
}

<#
Check to determine if the cluster name and IP is back online.
If so, display confirmation and exit as script is completed
If not, attempt to online the appropriate IP before completing.
#>

$clustername2 = Get-ClusterResource | Where-Object ResourceType -Like "Network Name" | Where-Object State -Like "Online" | Get-ClusterParameter | Where-Object Name -Like "Name"
if ($clustername2.value -eq $clustername.value) {
    Write-Host "Cluster name"$clustername.value"is online." -ForegroundColor Green
}else {
    Write-Host "Cluster name"$clustername.value"is not online. Attempting to online the cluster resource." -ForegroundColor Yellow
    try {
        Get-ClusterResource | Where-Object ResourceType -Like "Network Name" | Where-Object State -Like "Offline" | Start-ClusterResource -ErrorAction Stop
    }catch {
        Write-Host "Cluster name"$clustername.value"could not be brought online. Please bring the resource online manually using the Start-ClusterResource cmdlet." -ForegroundColor Red
    }finally {
        $clustername2 = Get-ClusterResource | Where-Object ResourceType -Like "Network Name" | Where-Object State -Like "Online" | Get-ClusterParameter | Where-Object Name -Like "Name" 
        if ($clustername2.value -eq $clustername.value) {
            Write-Host "Cluster name"$clustername.value"is online." -ForegroundColor Green
            Start-Sleep 1
        }
    }
}

$clusterip2 = Get-ClusterResource | Where-Object ResourceType -Like "IP Address" | Where-Object State -Like "Online" | Get-ClusterParameter | Where-Object Name -Like "Address"
if ($clusterip2.value -eq $clusterip.value) {
    Write-Host "Cluster IP"$clusterip.value"is online." -ForegroundColor Green
}else {
    Write-Host "Cluster IP"$clusterip.value"is not online. Attempting to online the cluster resource." -ForegroundColor Yellow
    try {
        Get-ClusterResource | Where-Object ResourceType -Like "IP Address" | Where-Object State -Like "Offline" | Start-ClusterResource -ErrorAction Stop
    }catch {
        Write-Host "Cluster IP"$clusterip.value"could not be brought online. Please bring the resource online manually using the Start-ClusterResource cmdlet." -ForegroundColor Red
    }finally {
        $clusterip2 = Get-ClusterResource | Where-Object ResourceType -Like "IP Address" | Where-Object State -Like "Online" | Get-ClusterParameter | Where-Object Name -Like "Address" 
        if ($clusterip2.value -eq $clusterip.value) {
            Write-Host "Cluster IP"$clusterip.value"is online." -ForegroundColor Green
        }
    }
}

Write-Host "Cleanup is complete." -ForegroundColor Green
}