ADLAB PowerShell source file: lib-hyperv.ps1

(C) Ondrej Sevecek, 2019 - www.sevecek.com, ondrej@sevecek.com



# ============================================================
# Note: Make sure that the lib-common has been loaded       ||
#                                                           ||

if (($global:adlabVersion -lt 1) -or (-not $global:libCommonScriptInitialized)) {

  $msgLibCommonNotInitialized = 'ADLAB: lib-common not loaded or initialized. Exiting'
  Write-Host $msgLibCommonNotInitialized -ForegroundColor Red
  throw $msgLibCommonNotInitialized
  exit 1
}

$adlabVersionThisLib = 235
$adlabReleaseDateThisLib = [DateTime]::Parse('2019-09-22')
DBG ('Library loaded: {0} | v{1} | {2:yyyy-MM-dd}' -f (Split-Path -leaf $MyInvocation.MyCommand.Definition), $adlabVersionThisLib, $adlabReleaseDateThisLib)

#                                                           ||
# Note: Make sure that the lib-common has been loaded       ||
# ============================================================


function global:Get-VMxMS ()
{
  [WMI] $vmms = $null

  DBGSTART
  $vmms = Get-WmiObject MSVM_VirtualSystemManagementService -namespace $global:virtualizationNamespace
  DBGER $MyInvocation.MyCommand.Name $error
  DBGEND
  DBGIF $MyInvocation.MyCommand.Name { Is-Null $vmms }
  
  DBG ('VMMS opened: api = {0} | installed = {1:s}' -f $global:virtualizationNamespace, [System.Management.ManagementDateTimeconverter]::ToDateTime($vmms.InstallDate))

  return $vmms
}


function global:Get-VMxSMS ()
{
  [WMI] $switchSvc = $null

  if ($global:virtualizationWmiAPIversion -eq 1) {
  
    DBGSTART
    $switchSvc = Get-WmiObject Msvm_VirtualSwitchManagementService -namespace $global:virtualizationNamespace
    DBGER $MyInvocation.MyCommand.Name $error
    DBGEND
    DBGIF $MyInvocation.MyCommand.Name { Is-Null $switchSvc }
  
    # Note: on windows 2008 R2 although the InstallDate member is present, it has an empty value
    #       while on Windows 2012 this contains something.
    #DBG ('VMSMS opened: api = {0} | installed = {1:s}' -f $global:virtualizationNamespace, [System.Management.ManagementDateTimeconverter]::ToDateTime($switchSvc.InstallDate))
    DBG ('VMSMS opened: api = {0}' -f $global:virtualizationNamespace)

  } else {

    DBGSTART
    $switchSvc = Get-WmiObject Msvm_VirtualEthernetSwitchManagementService -namespace $global:virtualizationNamespace
    DBGER $MyInvocation.MyCommand.Name $error
    DBGEND
    DBGIF $MyInvocation.MyCommand.Name { Is-Null $switchSvc }
  
    DBG ('VMSMS opened: api = {0} | installed = {1:s}' -f $global:virtualizationNamespace, [System.Management.ManagementDateTimeconverter]::ToDateTime($switchSvc.InstallDate))
  }

  DBGIF $MyInvocation.MyCommand.Name { Is-Null $switchSvc }

  return $switchSvc
}


function global:Get-VMxSecS ()
{
  [WMI] $vmsecs = $null

  DBG ('Going to open the VM security service')
  DBGSTART
  $vmsecs = Get-WmiObject MSVM_SecurityService -namespace $global:virtualizationNamespace
  DBGER $MyInvocation.MyCommand.Name $error
  DBGEND
  DBGIF $MyInvocation.MyCommand.Name { Is-Null $vmsecs }
  
  return $vmsecs
}


function global:New-VMxSettings ([int] $type, [string] $subType, [string] $elementName)
{
  [WMI] $newObj = $null

  $wmiAC = Get-WmiQuerySingleObject '.' "SELECT * FROM MSVM_AllocationCapabilities WHERE ResourceType = $type AND ResourceSubType = '$subType'" -namespace $global:virtualizationNamespace 

  if (Is-NonNull $wmiAC) {

    $ac = ($wmiAC.__Path).Replace('\', '\\')

    $connACSettings = Get-WmiQuerySingleObject '.' "SELECT * FROM MSVM_SettingsDefineCapabilities WHERE ValueRange = 0 AND GroupComponent = '$ac'" -namespace $global:virtualizationNamespace

    if (Is-NonNull $connACSettings) {
    
      DBGIF $MyInvocation.MyCommand.Name { Is-EmptyString $connACSettings.PartComponent }
      $newObj = New-Object System.Management.ManagementObject($connACSettings.PartComponent)

      if (Is-NonNull $newObj) {

        # Note: only in this case: http://msdn.microsoft.com/en-us/library/cc136877(v=vs.85).aspx
        #       And according to my experience, also the 'Microsoft Synthetic Ethernet Port' and 'Network Adapter'
        if (
            (($type -eq $hypervResourceSpecs['storResTypeSCSIBus']) -and ($subType -eq $hypervResourceSpecs['storSubTypeSCSIBus'])) -or
            (($type -eq $hypervResourceSpecs['ntwResTypeEthPort']) -and ($subType -eq $hypervResourceSpecs['ntwSubTypeEthPort']))
           ) {

          # Note: although this will result in double {} brackets, they must be there for it to work
          DBGSTART
          $newObj.VirtualSystemIdentifiers = @((Get-GuidString $true))
          DBGER $MyInvocation.MyCommand.Name $error
          DBGEND
        
        } else {

          #DBGIF $MyInvocation.MyCommand.Name { Contains-Safe (Get-Member -Input $newObj -MemberType Property) VirtualSystemIdentifiers Name }
        }

        $newObj.ElementName = $elementName
      }
    }
  }

  DBGIF $MyInvocation.MyCommand.Name { Is-Null $newObj }

  return $newObj
}


function global:Add-VMxResource ([WMI] $vm, [WMI] $vmSettingData)
{
  DBGIF $MyInvocation.MyCommand.Name { Is-Null $vm }
  DBGIF $MyInvocation.MyCommand.Name { Is-Null $vmSettingData }
  
  [WMI] $newResource = $null

  $vmms = Get-VMxMS
  DBG ('Add VM resources for VM: {0} | {1} | setting = {2}' -f $vm.ElementName, $vm.Name, $vmSettingData.__PATH)

  if ((Is-NonNull $vm) -and (Is-NonNull $vmSettingData) -and (Is-NonNull $vmms)) {

    if ($global:virtualizationWmiAPIversion -eq 1) {

      DBGSTART
      $res = $null
      $res = $vmms.AddVirtualSystemResources($vm.__PATH, $vmSettingData.GetText(1))
      DBGER $MyInvocation.MyCommand.Name $error
      DBGEND
      $jobRes = Wait-HyperVJob ([ref] $res)
      DBGIF $MyInvocation.MyCommand.Name { (Is-Null $res) -or ((Get-CountSafe $res.NewResources) -lt 1) }

      if ((Is-NonNull $res) -and ((Get-CountSafe $res.NewResources) -ge 1)) {

        DBGIF $MyInvocation.MyCommand.Name { (Get-CountSafe $res.NewResources) -gt 1 }
        DBGSTART
        $newResource = [wmi] ($res.NewResources[0])
        DBGER $MyInvocation.MyCommand.Name $error
        DBGEND
      }

    } else {

      DBGSTART
      $res = $null
      $res = $vmms.AddResourceSettings($vm.__PATH, $vmSettingData.GetText(1))
      DBGER $MyInvocation.MyCommand.Name $error
      DBGEND
      $jobRes = Wait-HyperVJob ([ref] $res)
      DBGIF $MyInvocation.MyCommand.Name { (Is-Null $res) -or ((Get-CountSafe $res.ResultingResourceSettings) -lt 1) }

      if ((Is-NonNull $res) -and ((Get-CountSafe $res.ResultingResourceSettings) -ge 1)) {

        DBGIF $MyInvocation.MyCommand.Name { (Get-CountSafe $res.ResultingResourceSettings) -gt 1 }
        DBGSTART
        $newResource = [wmi] ($res.ResultingResourceSettings[0])
        DBGER $MyInvocation.MyCommand.Name $error
        DBGEND
      }
    }
  }

  DBG ('New VM resource added: {0}' -f $newResource.__PATH)
  DBGIF $MyInvocation.MyCommand.Name { Is-Null $newResource }

  return $newResource
}


function global:Modify-VMxResource ([WMI] $vm, [WMI] $vmSettingData) 
{
  DBGIF $MyInvocation.MyCommand.Name { Is-Null $vm }
  DBGIF $MyInvocation.MyCommand.Name { Is-Null $vmSettingData }
  
  $vmms = Get-VMxMS
  DBG ('Modify VM resources for VM: {0} | {1} | setting = {2}' -f $vm.ElementName, $vm.Name, $vmSettingData.__PATH)

  if ((Is-NonNull $vm) -and (Is-NonNull $vmSettingData) -and (Is-NonNull $vmms)) {

    if ($global:virtualizationWmiAPIversion -eq 1) {
         
      DBGSTART  
      $res = $vmms.ModifyVirtualSystemResources($vm, $vmSettingData.GetText(1))
      DBGER $MyInvocation.MyCommand.Name $error
      DBGEND
      $jobRes = Wait-HyperVJob ([ref] $res)

    } else {

      DBGSTART  
      $res = $vmms.ModifyResourceSettings($vmSettingData.GetText(1))
      DBGER $MyInvocation.MyCommand.Name $error
      DBGEND
      $jobRes = Wait-HyperVJob ([ref] $res)
    }
  }
}


function global:Get-MacAddressDotted ([string] $mac)
{
  DBGIF ('Invalid MAC address: {0}' -f $mac) { $mac.Length -ne 12 }
  return ('{0}:{1}:{2}:{3}:{4}:{5}' -f $mac.SubString(0, 2), $mac.SubString(2, 2), $mac.SubString(4, 2), $mac.SubString(6, 2), $mac.SubString(8, 2), $mac.SubString(10, 2))
}


function global:Get-VMxMSSettings ()
{
  DBG ('{0}: Parameters: {1}' -f $MyInvocation.MyCommand.Name, (($PSBoundParameters.Keys | ? { $_ -ne 'object' } | % { '{0}={1}' -f $_, $PSBoundParameters[$_]}) -join $multivalueSeparator))

  [WMI] $msSettings = $null

  DBGSTART
  $msSettings = Get-WmiObject Msvm_VirtualSystemManagementServiceSettingData -namespace $global:virtualizationNamespace
  DBGER $MyInvocation.MyCommand.Name $error
  DBGEND
  DBGIF $MyInvocation.MyCommand.Name { Is-Null $msSettings }
  
  DBG ('VMMS settings obtained: api = {0} | vhd = {1} | ext = {2} | minMac = {3} | maxMac = {4} | numa = {5} | esm = {6}' -f $global:virtualizationNamespace, $msSettings.DefaultVirtualHardDiskPath, $msSettings.DefaultExternalDataRoot, $msSettings.MinimumMacAddress, $msSettings.MaximumMacAddress, $msSettings.NumaSpanningEnabled, $msSettings.EnhancedSessionModeEnabled)

  # Note: I believe that the enhanced session mode is available since 2012 R2 only
  DBGIF $MyInvocation.MyCommand.Name { (Is-Null $msSettings.EnhancedSessionModeEnabled) -and ($global:thisOSVersionNumber -ge 6.3) }
  DBGIF $MyInvocation.MyCommand.Name { (Is-NonNull $msSettings.EnhancedSessionModeEnabled) -and ($global:thisOSVersionNumber -lt 6.3) }

  return $msSettings
}



function global:Get-VMxSettings ($vm, [switch] $global, [bool] $includeSnapshots, [ref] $dispoList)
{
  DBG ('{0}: Parameters: {1}' -f $MyInvocation.MyCommand.Name, (($PSBoundParameters.Keys | ? { $_ -ne 'object' } | % { '{0}={1}' -f $_, $PSBoundParameters[$_]}) -join $multivalueSeparator))

  $vmSettings = $null

  if (Is-NonNull $vm) {

    DBG ('Get VM settings for: {0} | {1}' -f $vm.ElementName, $vm.Name)

    if ($global:virtualizationWmiAPIversion -eq 2) {

      DBGIF $MyInvocation.MyCommand.Name { $global }

      $allVmSettings = Get-WMIRelated $vm Msvm_VirtualSystemSettingData
      # This would mean there are snapshots. The Active Settings for the virtual machine are pointing to a first snapshot through this member
      # then the snapshot points up to another snapshot etc.
      DBGIF $MyInvocation.MyCommand.Name { ((Get-CountSafe $allVmSettings) -eq 1) -and (Is-ValidString $vmSettings.Parent) }

      $vmSettings = $allVmSettings | where { 
        ($_.VirtualSystemType -eq 'Microsoft:Hyper-V:System:Realized') -or 
        (($_.VirtualSystemType -eq 'Microsoft:Hyper-V:Snapshot:Realized') -and $includeSnapshots)
        }
      DBGIF $MyInvocation.MyCommand.Name { (-not $includeSnapshots) -and ((Get-CountSafe $vmSettings) -gt 1) }

    } else {

      if ($global) {

        $vmSettings = Get-WMIRelatedSingle $vm Msvm_VirtualSystemGlobalSettingData
        DBGIF $MyInvocation.MyCommand.Name { Is-ValidString $vmSettings.Parent }
      
      } else {

        $allVmSettings = Get-WMIRelated $vm Msvm_VirtualSystemSettingData
        DBGIF $MyInvocation.MyCommand.Name { ((Get-CountSafe $allVmSettings) -eq 1) -and (Is-ValidString $vmSettings.Parent) }

        $vmSettings = $allVmSettings | where { ($_.SettingType -eq 3) -or (($_.SettingType -eq 5) -and $vmSettings) }
        DBGIF $MyInvocation.MyCommand.Name { (-not $includeSnapshots) -and ((Get-CountSafe $vmSettings) -gt 1) }
      }
    }
  }

  DBGIF $MyInvocation.MyCommand.Name { Is-Null $vmSettings }

  if (Is-NonNull $dispoList) {

    [void] $dispoList.Value.Add($vmSettings)
  }

  if (-not $includeSnapshots) {

    DBG ('Returning VM settings: {0}' -f (Is-NonNull $vmSettings))
    DBGIF $MyInvocation.MyCommand.Name { (Get-CountSafe $vmSettings) -gt 1 }
    return $vmSettings

  } else {

    DBG ('Returning VM settings and snapshots: {0}' -f (Get-CountSafe $vmSettings))
    return ,$vmSettings
  }
}


function global:Get-VMxDefaultSettings ([int] $type, [string] $subType, [string] $assertClass)
{
  DBG ('{0}: Parameters: {1}' -f $MyInvocation.MyCommand.Name, (($PSBoundParameters.Keys | ? { $_ -ne 'object' } | % { '{0}={1}' -f $_, $PSBoundParameters[$_]}) -join $multivalueSeparator))
  DBGIF $MyInvocation.MyCommand.Name { IS-EmptyString $subType }

  [WMI] $defaultObj = $null

  $resPool = Get-WmiQuerySingleObject '.' ('SELECT * FROM Msvm_ResourcePool WHERE ResourceType = {0} AND ResourceSubType = "{1}" AND Primordial = True' -f $type, $subType) -namespace $global:virtualizationNamespace

  $allocCapa = Get-WmiRelatedSingle $resPool 'Msvm_AllocationCapabilities'
  $defaultSettings = (Get-WMIRelationship $allocCapa 'Msvm_SettingsDefineCapabilities') | ? { $_.ValueRole -eq 0 }

  DBGSTART
  $defaultObj = [WMI] $defaultSettings.PartComponent
  DBGER $MyInvocation.MyCommand.Name $Error
  DBGEND

  DBGIF $MyInvocation.MyCommand.Name { Is-Null $defaultObj }
  DBGIF $MyInvocation.MyCommand.Name { $defaultObj.Caption -notlike '*Default Settings' }
  DBGIF $MyInvocation.MyCommand.Name { $defaultObj.ResourceSubType -ne $subType }
  DBGIF $MyInvocation.MyCommand.Name { $defaultObj.ResourceType -ne $type }

  DBGIF ('Weird result class: {0}' -f $defaultObj.__CLASS) { (Is-ValidSTring $assertClass) -and ($defaultObj.__CLASS -ne $assertClass) }

  DBG ('Returning default settings: {0}' -f $defaultObj.Caption)

  return $defaultObj
}


function global:Get-VHDInfo ([string] $vhd)
{
  DBG ('{0}: Parameters: {1}' -f $MyInvocation.MyCommand.Name, (($PSBoundParameters.Keys | ? { $_ -ne 'object' } | % { '{0}={1}' -f $_, $PSBoundParameters[$_]}) -join $multivalueSeparator))

  DBGIF $MyInvocation.MyCommand.Name { Is-EmptyString $vhd }
  [PSCustomObject] $vhdInfo = $null

  if (Is-ValidString $vhd) {

    DBG ('Open the WMI VHD service')
    DBGSTART
    $vhdSvc = Get-WmiObject Msvm_ImageManagementService -namespace $global:virtualizationNamespace
    DBGER $MyInvocation.MyCommand.Name $error
    DBGEND

    if (Is-NonNull $vhdSvc) {

      if ($global:virtualizationWmiAPIversion -eq 1) {

        DBG ('Get the info with v1 API for disk: {0}' -f $vhd)
        DBGSTART
        $wmiInfo = $vhdSvc.GetVirtualHardDiskInfo($vhd)
        DBGER $MyInvocation.MyCommand.Name $error
        DBGEND
        DBGIF ('Ivalid status code: {0}' -f $wmiInfo.ReturnValue) { $wmiInfo.ReturnValue -ne 0 }

        if (Is-NonNull $wmiInfo) {

          DBGSTART
          $vhdInfoXml = [xml] $wmiInfo.Info
          DBGER $MyInvocation.MyCommand.Name $error
          DBGEND

          DBGIF $MyInvocation.MyCommand.Name { $vhdInfoXml.INSTANCE.CLASSNAME -ne 'Msvm_VirtualHardDiskInfo' }

          $vhdInfo = New-Object PSCustomObject
          
          DBGSTART
          Add-Member -InputObject $vhdInfo -MemberType NoteProperty -Name type -Value $global:vhdType[([int] $vhdInfoXml.SelectSingleNode("INSTANCE/PROPERTY[@NAME='Type']/VALUE").'#text')]
          DBGER $MyInvocation.MyCommand.Name $error
          DBGEND

          Add-Member -InputObject $vhdInfo -MemberType NoteProperty -Name parent -Value $vhdInfoXml.SelectSingleNode("INSTANCE/PROPERTY[@NAME='ParentPath']/VALUE").'#text'
        }
      
      } else {

        DBG ('Get the info with v2 API for disk: {0}' -f $vhd)
        DBGSTART
        $wmiInfo = $vhdSvc.GetVirtualHardDiskSettingData($vhd)
        DBGER $MyInvocation.MyCommand.Name $error
        DBGEND
        DBGIF ('Ivalid status code: {0}' -f $wmiInfo.ReturnValue) { $wmiInfo.ReturnValue -ne 0 }

        if (Is-NonNull $wmiInfo) {

          DBGSTART
          $vhdInfoXml = [xml] $wmiInfo.SettingData
          DBGER $MyInvocation.MyCommand.Name $error
          DBGEND

          DBGIF $MyInvocation.MyCommand.Name { $vhdInfoXml.INSTANCE.CLASSNAME -ne 'Msvm_VirtualHardDiskSettingData' }

          $vhdInfo = New-Object PSCustomObject

          DBGSTART
          Add-Member -InputObject $vhdInfo -MemberType NoteProperty -Name type -Value $global:vhdType[([int] $vhdInfoXml.SelectSingleNode("INSTANCE/PROPERTY[@NAME='Type']/VALUE").'#text')]
          DBGER $MyInvocation.MyCommand.Name $error
          DBGEND

          Add-Member -InputObject $vhdInfo -MemberType NoteProperty -Name parent -Value $vhdInfoXml.SelectSingleNode("INSTANCE/PROPERTY[@NAME='ParentPath']/VALUE").'#text'
        }
      }
    }
  }

  DBGIF $MyInvocation.MyCommand.Name { Is-Null $vhdInfo }
  DBGIF $MyInvocation.MyCommand.Name { ($vhdInfo.type -eq 'diff') -and (Is-EmptyString $vhdInfo.parent) }
  DBGIF $MyInvocation.MyCommand.Name { (Is-ValidString $vhdInfo.parent) -and ((Resolve-PathSafe $vhdInfo.parent) -ne $vhdInfo.parent) }

  DBG ('VHD parameters: type = {0} | parent = {1}' -f $vhdInfo.type, $vhdInfo.parent)
  return $vhdInfo
}


function global:Create-VHD ([string] $newDisk, [string] $baseDisk, [int64] $size, [bool] $vhdxIfPossible = $false, [int] $logicalSectorSize = 0, [bool] $fixed)
{
  DBG ('{0}: Parameters: {1}' -f $MyInvocation.MyCommand.Name, (($PSBoundParameters.Keys | ? { $_ -ne 'object' } | % { '{0}={1}' -f $_, $PSBoundParameters[$_]}) -join $multivalueSeparator))

  DBGIF $MyInvocation.MyCommand.Name { Is-EmptyString $newDisk }
  DBGIF $MyInvocation.MyCommand.Name { (Is-ValidString $baseDisk) -and (-not (Test-Path $baseDisk)) }
  DBGIF $MyInvocation.MyCommand.Name { Test-Path $newDisk }

  [string] $status = $null

  DBG ('Open the WMI VHD service')
  DBGSTART
  $vhdSvc = Get-WmiObject Msvm_ImageManagementService -namespace $global:virtualizationNamespace
  DBGER $MyInvocation.MyCommand.Name $error
  DBGEND

  [int64] $sizePadded = ([Math]::Floor($size / 512)) * 512
  if (($size % 512) -gt 0) {

    $sizePadded += 512
  }

  DBG ('The size must be multiple of 512 or "invalid parameter" error is raised: {0} | {1}' -f $size, $sizePadded)
  $size = $sizePadded

  if (Is-EmptyString ([IO.Path]::GetExtension($newDisk))) {

    DBG ('Must assign VHD extension to the file name without extension: {0}' -f $newDisk)
    $newDisk = [IO.Path]::ChangeExtension($newDisk, 'vhd')
    DBG ('Assigned VHD extension: {0}' -f $newDisk)
  
  } elseif (([IO.Path]::GetExtension($newDisk)) -eq '.vhdx') {

    $vhdxIfPossible = $true
  }


  if (Is-NonNull $vhdSvc) {

    if (Is-ValidString $baseDisk) {

      DBG ('Base disk specified, must normalize: {0}' -f $baseDisk)
      $baseDisk = Get-FirstPathFromWildcard (Resolve-PathSafe $baseDisk)
    }


    if ($global:virtualizationWmiAPIversion -eq 1) {

      DBG ('Create VHD with 6.1 logic')
      
      if ($vhdxIfPossible) {

        DBG ('We cannot create VHDX on Windows 2008, trim terminating X from name if necessary: {0}' -f $newDisk)
        if ($newDisk -like '*.vhdx') {

          DBGSTART
          $newDisk = [IO.Path]::ChangeExtension($newDisk, 'vhd')
          DBGER $MyInvocation.MyCommand.Name $error
          DBGEND
          DBG ('Disk extension changed: {0}' -f $newDisk)
        }
      }
      
      if (Is-ValidString $baseDisk) {

        DBG ('Differencing VHD: {0}' -f $baseDisk)
        DBGIF $MyInvocation.MyCommand.Name { $size -gt 0 }
        DBGSTART
        $res = $vhdSvc.CreateDifferencingVirtualHardDisk($newDisk, $baseDisk)
        DBGER $MyInvocation.MyCommand.Name $error
        DBGEND
        $jobRes = Wait-HyperVJob ([ref] $res)

      } else {

        if (-not $fixed) {

          DBG ('Dynamically expanding VHD')
          DBGIF $MyInvocation.MyCommand.Name { $size -le 0 }
          DBGSTART
          $res = $vhdSvc.CreateDynamicVirtualHardDisk($newDisk, $size)
          DBGER $MyInvocation.MyCommand.Name $error
          DBGEND
          $jobRes = Wait-HyperVJob ([ref] $res)

        } else {

          DBG ('Fixed VHD')
          DBGIF $MyInvocation.MyCommand.Name { $size -le 0 }
          DBGSTART
          $res = $vhdSvc.CreateFixedVirtualHardDisk($newDisk, $size)
          DBGER $MyInvocation.MyCommand.Name $error
          DBGEND
          $jobRes = Wait-HyperVJob ([ref] $res)
        }
      }

      if ($jobRes.ErrorCode -eq 0) { 

        $status = $newDisk
      }

    } else {

      DBG ('Create VHD with 6.2+ logic')

      $vhdSettingData = Spawn-WMIInstance '.' Msvm_VirtualHarddiskSettingData $global:virtualizationNamespace
      DBGIF $MyInvocation.MyCommand.Name { Is-Null $vhdSettingData }

      if (Is-NonNull $vhdSettingData) {

        if ($vhdxIfPossible) {

          DBG ('We should create VHDX, ensure the extension is VHDX: {0}' -f $newDisk)
          if ($newDisk -like '*.vhd') {

            DBGSTART
            $newDisk = [IO.Path]::ChangeExtension($newDisk, 'vhdx')
            DBGER $MyInvocation.MyCommand.Name $error
            DBGEND
            DBG ('Disk extension changed: {0}' -f $newDisk)
          }
        }
      

        DBG ('Create VHD with the specified setting data')

        if (Is-ValidString $baseDisk) {

          DBG ('Differencing VHD: {0}' -f $baseDisk)
          DBGIF $MyInvocation.MyCommand.Name { $size -gt 0 }
          DBGSTART
          $vhdSettingData.ParentPath = $baseDisk.Replace('\', '\\')
          $vhdSettingData.Type = 4   # 2 = fixed, 3 = dynamic, 4 = differencing
          DBGER $MyInvocation.MyCommand.Name $error
          DBGEND

          if (([IO.Path]::GetExtension($baseDisk)) -eq '.vhdx') {

            DBG ('The base disk seems to be VHDX we have to go for VHDX child')
            $vhdxIfPossible = $true
          }

        } else {

          if (-not $fixed) {

            DBG ('Dynamically expanding VHD')
            DBGIF $MyInvocation.MyCommand.Name { $size -le 0 }
            DBGSTART
            $vhdSettingData.MaxInternalSize = $size
            $vhdSettingData.Type = 3   # 2 = fixed, 3 = dynamic, 4 = differencing
            DBGER $MyInvocation.MyCommand.Name $error
            DBGEND

          } else {

            DBG ('Fixed VHD')
            DBGIF $MyInvocation.MyCommand.Name { $size -le 0 }
            DBGSTART
            $vhdSettingData.MaxInternalSize = $size
            $vhdSettingData.Type = 2   # 2 = fixed, 3 = dynamic, 4 = differencing
            DBGER $MyInvocation.MyCommand.Name $error
            DBGEND
          }
        }


        if ($vhdxIfPossible) {

          DBG ('Define VHDX: {0}' -f $newDisk)
          DBGSTART
          $vhdSettingData.Path = $newDisk.Replace('\', '\\')
          $vhdSettingData.Format = 3 # 2 = VHD, 3 = VHDX
          DBGER $MyInvocation.MyCommand.Name $error
          DBGEND

          if ($logicalSectorSize -gt 0) {

            DBG ('Define logical sector size: {0}' -f $logicalSectorSize)
            DBGSTART
            $vhdSettingData.LogicalSectorSize = $logicalSectorSize
            DBGER $MyInvocation.MyCommand.Name $error
            DBGEND
          }

        } else {

          DBG ('Define VHD: {0}' -f $newDisk)
          DBGSTART
          $vhdSettingData.Path = $newDisk.Replace('\', '\\')
          $vhdSettingData.Format = 2 # 2 = VHD, 3 = VHDX
          DBGER $MyInvocation.MyCommand.Name $error
          DBGEND
        }

        DBG ('Create the VHD(X) finally')
        DBGSTART
        $res = $vhdSvc.CreateVirtualHardDisk($vhdSettingData.GetText(1))
        DBGER $MyInvocation.MyCommand.Name $error
        DBGEND
        $jobRes = Wait-HyperVJob ([ref] $res)

        if ($jobRes.ErrorCode -eq 0) { 

          $status = $newDisk
        }
      }
    }
  }

  DBG ('VHD created successfully: {0}' -f $status)
  DBGIF $MyInvocation.MyCommand.Name { Is-EmptyString $status }

  return $status
}


function global:Mount-VMxDisk ($vm, $drive, [string] $diskPath, [string] $diskType)
{
  DBG ('{0}: Parameters: {1}' -f $MyInvocation.MyCommand.Name, (($PSBoundParameters.Keys | ? { $_ -ne 'object' } | % { '{0}={1}' -f $_, $PSBoundParameters[$_]}) -join $multivalueSeparator))

  DBGIF $MyInvocation.MyCommand.Name { Is-Null $vm }
  DBGIF $MyInvocation.MyCommand.Name { Is-Null $drive }
  DBGIF $MyInvocation.MyCommand.Name { Is-EmptyString $diskPath }

  if ((Is-NonNull $vm) -and (Is-NonNull $drive) -and (Is-ValidString $diskPath)) {

    $vmms = Get-VMxMS
    # 3 = currrent settings, 5 = snapshot
    $sysSettings = Get-VMxSettings $vm
    $mountedDisk = (Get-WMIRelated $sysSettings $hypervResourceSpecs['storClassVHDVFDISO']) | where {$_.Parent -eq $drive}
  
    if (Is-NonNull $mountedDisk)
    {
      DBG ('There is already a disk mounted on the drive. Dismount: {0}' -f $mountedDisk."$($hypervResourceSpecs['storPathVHDVFDISO'])")

      $res = $vmms.RemoveVirtualSystemResources($vm, @($mountedDisk))
      $jobRes = Wait-HyperVJob ([ref] $res)
    }

    switch ($diskType)
    {
      'iso' { $newDisk = New-VMxSettings $hypervResourceSpecs['storResTypeVHDVFDISO'] $hypervResourceSpecs['storSubTypeISO'] $hypervResourceSpecs['ISO Disk Image'] }
      'vhd' { $newDisk = New-VMxSettings $hypervResourceSpecs['storResTypeVHDVFDISO'] $hypervResourceSpecs['storSubTypeVHD'] $hypervResourceSpecs['VHD Disk Image'] }
      'vfd' { $newDisk = New-VMxSettings $hypervResourceSpecs['storResTypeVHDVFDISO'] $hypervResourceSpecs['storSubTypeVFD'] $hypervResourceSpecs['Floppy Disk Image'] }
    }


    DBG ('Should mount (possibly wildcard) specified disk: {0} | exists = {1}' -f $diskPath, (Test-Path $diskPath))
  
    DBGSTART
    $realDiskPath = Get-FirstPathFromWildcard (Resolve-PathSafe $diskPath)
    DBGER $MyInvocation.MyCommand.Name $error
    DBGEND

    DBG ('Will mount the following real disk path: {0}' -f $realDiskPath)
    DBGIF $MyInvocation.MyCommand.Name { (Is-EmptyString $realDiskPath) }

    if (Is-ValidString $realDiskPath) {

      DBGIF $MyInvocation.MyCommand.Name { -not (Test-Path $realDiskPath) }

      $newDisk.Parent = $drive
      $newDisk."$($hypervResourceSpecs['storPathVHDVFDISO'])" = @($realDiskPath)
 
      $res = Add-VMxResource $vm $newDisk
    }
  }
}



function global:Assert-VMxDrive ($vm, [string] $driveType, [string] $busType, [int] $busAddress, [int] $driveAddress)
{
  DBG ('{0}: Parameters: {1}' -f $MyInvocation.MyCommand.Name, (($PSBoundParameters.Keys | ? { $_ -ne 'object' } | % { '{0}={1}' -f $_, $PSBoundParameters[$_]}) -join $multivalueSeparator))

  $drive = $null

  DBG ('VM to work with: {0} | {1}' -f $vm.ElementName, $vm.Name)
  DBG ('Asserting drive: {0} | Bus type: {1} | Bus address: {2} | Drive address: {3}' -f $driveType, $busType, $busAddress, $driveAddress)

  $vmms = Get-VMxMS
  $sysSettings = Get-VMxSettings $vm
  DBGIF $MyInvocation.MyCommand.Name { (Is-Null $sysSettings) -or ((Get-CountSafe $sysSettings) -ne 1) }

  $vmGen = Get-VMxGeneration $vm
  DBGIF $MyInvocation.MyCommand.Name { ($vmGen -ge 2) -and (($driveType -eq 'fdd') -or ($busType -eq 'fdd')) }

  if (($vmGen -ge 2) -and (($driveType -eq 'fdd') -or ($busType -eq 'fdd'))) {

    DBG ('Floppy drives not supported on gen2 machines')
    return $null
  }


  switch ($driveType) {

    'dvd' { $driveTypeID = $hypervResourceSpecs['storResTypeDVD'] ; $driveTypeName = $hypervResourceSpecs['storSubTypeDVD'] ; $driveName = 'DVD Drive' }
    'hd' { $driveTypeID = $hypervResourceSpecs['storResTypeDisk'] ; $driveTypeName = $hypervResourceSpecs['storSubTypeDisk'] ; $driveName = 'Hard Disk Drive' }
    'fdd' { $driveTypeID = $hypervResourceSpecs['storResTypeFDD'] ; $driveTypeName = $hypervResourceSpecs['storSubTypeFDD'] ; $driveName = 'Diskette Drive' }
    default { DBGIF 'Error switching drive type' { $true } }
  }


  switch ($busType) {

    'ide' { $busResourceType = $hypervResourceSpecs['storResTypeIDEBus'] ; $busResourceName = $hypervResourceSpecs['storSubTypeIDEBus'] ; $busName = 'IDE Controller ' + $busAddress }
    'scsi' { $busResourceType = $hypervResourceSpecs['storResTypeSCSIBus'] ; $busResourceName = $hypervResourceSpecs['storSubTypeSCSIBus'] ; $busName = 'SCSI Controller ' + $busAddress }
    'fdd' { $busResourceType = $hypervResourceSpecs['storResTypeFDDBus'] ; $busResourceName = $hypervResourceSpecs['storSubTypeFDDBus'] ; $busName = 'Diskette Controller' }
    default { DBGIF 'Error switching bus type' { $true } }
  }


  DBG ('Will assert drive on: {0} | {1} | {2} | {3} | {4} | {5}' -f $driveTypeID, $driveTypeName, $driveName, $busResourceType, $busResourceName, $busName)


  if ($busType -ne 'fdd') {
  
    DBG ('Non FDD bus')

    [bool] $openExistingBus = $busAddress -ge 0

    if ($busAddress -lt 0) {

      DBG ('Bus address not specified, going to get the next free bus ID possible')
      [System.Collections.ArrayList] $busses = @()
      (Get-WMIRelated $sysSettings 'Msvm_ResourceAllocationSettingData') | where { ($_.ResourceType -eq $busResourceType) -and ($_.ResourceSubType -eq $busResourceName) } | % { [void] $busses.Add($_) }
      DBG ('Existing bus(es) found: {0}' -f (Get-CountSafe $busses))

      $highestAdr = -1
      foreach ($oneBus in $busses) {

        if ($oneBus."$($hypervResourceSpecs['storBusAddress'])" -ne $null) {

          $highestAdr = [Math]::Max($highestAdr, $oneBus."$($hypervResourceSpecs['storBusAddress'])")
      
        } else {

          $highestAdr ++
        }
      }
    
      DBGIF $MyInvocation.MyCommand.Name { ($busType -ne 'ide') -and ($busType -ne 'scsi') }
      DBGIF ('Weirdly detected more than 2 IDE controllers at known maximum: {0}' -f $highestAdr) { ($busType -eq 'ide') -and ($highestAdr -gt 1) }
      DBGIF ('Weirdly detected more than 4 SCSI controllers at known maximum: {0}' -f $highestAdr) { ($busType -eq 'scsi') -and ($highestAdr -gt 3) }

      if ((($busType -eq 'ide') -and ($highestAdr -ge 1)) -or (($busType -eq 'scsi') -and ($highestAdr -ge 3))) {

        $busAddress = $highestAdr
        $openExistingBus = $true
        DBG ('Bus address used of the last existing bus at maximum: {0} | {1}' -f $busType, $busAddress)
      
      } else {

        $busAddress = $highestAdr + 1
        DBG ('The next free bus address determined as: {0} | {1}' -f $busType, $busAddress)
      }


      # must get updated after $busAddress changes to the new one  
      switch ($busType) {

        'ide' { $busName = 'IDE Controller ' + $busAddress }
        'scsi' { $busName = 'SCSI Controller ' + $busAddress }
      }

      DBG ('Bus name going to be: {0}' -f $busName)
    }

    if ($openExistingBus) {

      DBGIF ('Up to 2 IDE controllers at maximum: {0}' -f $busAddress) { ($busType -eq 'ide') -and ($busAddress -gt 1) }
      DBGIF ('Up to 4 SCSI controllers at maximum: {0}' -f $busAddress) { ($busType -eq 'scsi') -and ($busAddress -gt 3) }

      DBG ('Bus address specified, going to open the device if existing: {0}' -f $busAddress)
      DBGIF $MyInvocation.MyCommand.Name { Is-EmptyString $busType }

      # This is not working as .Address is empty on SCSI busses (at least on Windows 2012)
      #$bus = (Get-WMIRelated $sysSettings 'Msvm_ResourceAllocationSettingData') | where { ($_.ResourceType -eq $busResourceType) -and ($_.ResourceSubType -eq $busResourceName) -and ($_.Address -eq $busAddress) }
      [System.Collections.ArrayList] $busses = @()
      (Get-WMIRelated $sysSettings 'Msvm_ResourceAllocationSettingData') | where { ($_.ResourceType -eq $busResourceType) -and ($_.ResourceSubType -eq $busResourceName) } | % { [void] $busses.Add($_) }
      DBG ('Existing bus(es) found: {0}' -f (Get-CountSafe $busses))

      if ((Get-CountSafe $busses) -ge ($busAddress + 1)) {

        $bus = $busses[$busAddress]
        DBG ('Existing bus selected: {0} | {1}' -f $bus.ElementName, $bus.InstanceId)
      }

    }

  } else {

    DBG ('FDD bus type. Get the bus device.')
    DBGIF $MyInvocation.MyCommand.Name { $driveType -ne 'fdd' }
    $drive = (Get-WMIRelated $sysSettings 'Msvm_ResourceAllocationSettingData') | where { ($_.ResourceType -eq $driveTypeID) -and ($_.ResourceSubType -eq $driveTypeName) }
    DBGIF $MyInvocation.MyCommand.Name { Is-Null $drive }
  }


  if (Is-Null $drive) {
  
    if (Is-Null $bus) {

      DBG ('Bus not yet opened, create a new one: type = {0} | name = {1} | bus = {2} | addr = {3}' -f $busResourceType, $busResourceName, $busName, $busAddress)
      DBGIF $MyInvocation.MyCommand.Name { $busType -eq 'fdd' }

      $newObj = New-VMxSettings $busResourceType $busResourceName $busName
      if (Is-NonNull $newObj) {

        DBG ('Assign the bus address: {0} | {1}' -f $hypervResourceSpecs['storBusAddress'], $busAddress)
        DBGSTART
        $newObj."$($hypervResourceSpecs['storBusAddress'])" = $busAddress
        DBGER ('Error on new VM settings: {0}' -f ($newObj | fl * | Out-String)) $error
        DBGEND

        $bus = Add-VMxResource $vm $newObj
      }
    }


    if (Is-NonNull $bus) {

      DBG ('Existing bus opened ok. Proceed to a drive')

      if ($driveAddress -ge 0) {

        #DBG ('Test if a drive exists at the drive address supplied: driveAddress = {0} | busAddress = {1} | busType = {2} | busSubType = {3}' -f $driveAddress, $bus.Address, $bus.ResourceType, $bus.ResourceSubType)
        #$existingDrive = (Get-WMIRelated $sysSettings 'Msvm_ResourceAllocationSettingData') | where { ($_.Parent -eq $bus) -and ($_.Address -eq $driveAddress) }

        DBG ('Test if a drive exists at the drive address supplied: driveAddress = {0} | busAddress = {1} | busType = {2} | busSubType = {3}' -f $driveAddress, ($bus."$($hypervResourceSpecs['storBusAddress'])"), $bus.ResourceType, $bus.ResourceSubType)

        # Note: the Address is not present on the just-created $bus object
        #       but everything works fine
        #DBGIF $MyInvocation.MyCommand.Name { Is-EmptyString ($bus."$($hypervResourceSpecs['storBusAddress'])") }

        $existingDrive = (Get-WMIRelated $sysSettings 'Msvm_ResourceAllocationSettingData') | where { ($_.Parent -eq $bus) -and ($_."$($hypervResourceSpecs['storBusAddress'])" -eq $driveAddress) }
        DBG ('Existing drive found: {0}' -f (Is-NonNull $existingDrive))
        DBGIF $MyInvocation.MyCommand.Name { (Get-CountSafe $existingDrive) -gt 1 }

        if (Is-NonNull $existingDrive) {

          DBG ('Existing drive found. Will remove: {0}' -f $existingDrive.ElementName)
          
          $existingDisks = (Get-WMIRelated $sysSettings 'Msvm_ResourceAllocationSettingData') | where { $_.Parent -eq $existingDrive }
    
          if (Is-NonNull $existingDisks) {

            DBG ('There are existing disks on the drive: {0}' -f (Get-CountSafe $existingDisks))

            foreach ($existingDisk in $existingDisks) {

              DBG ('Remove the existing disk first: {0} | {1}' -f $existingDisk."$($hypervResourceSpecs['storBusAddress'])", $existingDisk.ElementName)

              $res = $vmms.RemoveVirtualSystemResources($vm, @($existingDisk))
              $jobRes = Wait-HyperVJob ([ref] $res)
            }
          }
    
          DBG ('Finally remove the existing drive')

          $res = $vmms.RemoveVirtualSystemResources($vm, @($existingDrive))
          $jobRes = Wait-HyperVJob ([ref] $res)
        }
      
      } else {

        DBGIF $MyInvocation.MyCommand.Name { ($busType -ne 'ide') -and ($busType -ne 'scsi') }

        DBG ('The next free drive position should be used to attach the drive')
        [int[]] $existingDrives = (Get-WMIRelated $sysSettings 'Msvm_ResourceAllocationSettingData') | where { ($_.Parent -eq $bus) } | % { 
          
          $oneAddr = $_."$($hypervResourceSpecs['storBusAddress'])"
          DBGIF $MyInvocation.MyCommand.Name { Is-Null $oneAddr }
          Write-Output $oneAddr
        }

        DBG ('Existing drives found on the bus: {0} | #{1} | {2}' -f $bus, $existingDrives.Length, ($existingDrives -join ','))

        $highestDrive = -1
        foreach ($oneExistingDrive in $existingDrives) {

          $highestDrive = [Math]::Max($highestDrive, $oneExistingDrive)
        }

        $driveAddress = $highestDrive + 1
        DBG ('The new drive position has been determined as: existing = {0} | new = {1}' -f $highestDrive, $driveAddress)
      }
    
      DBGIF ('Invalid drive address for the bus type: {0} | {1}' -f $busType, $driveAddress) { (($busType -eq 'ide') -and ($driveAddress -gt 1)) -or (($busType -eq 'scsi') -and ($driveAddress -gt 63)) }

      DBG ('Create a new drive: {0} | {1} | {2}' -f $driveTypeID, $driveTypeName, $driveName)

      $newObj = New-VMxSettings $driveTypeID $driveTypeName $driveName
      
      $newObj."$($hypervResourceSpecs['storBusAddress'])" = $driveAddress
      $newObj.Parent = $bus
    
      $drive = Add-VMxResource $vm $newObj
    }
  }

  DBG ('Asserted drive: {0} | {1} | {2}' -f $drive.ResourceType, $drive.ResourceSubType, $drive."$($hypervResourceSpecs['storBusAddress'])")
  
  return $drive
}



function global:Attach-VMxFDD ($vm, $path)
{
  DBG ('{0}: Parameters: {1}' -f $MyInvocation.MyCommand.Name, (($PSBoundParameters.Keys | ? { $_ -ne 'object' } | % { '{0}={1}' -f $_, $PSBoundParameters[$_]}) -join $multivalueSeparator))

  DBGIF $MyInvocation.MyCommand.Name { Is-Null $vm }
  DBGIF $MyInvocation.MyCommand.Name { Is-EmptyString $path }

  if ((Is-NonNull $vm) -and (Is-ValidString $path)) {

    $fddDrive = Assert-VMxDrive $vm 'fdd' 'fdd' 0 0

    Mount-VMxDisk $vm $fddDrive $path 'vfd'
  }
}



function global:Attach-VMxISO ($vm, $path, $controller, $ctrlID, $deviceID)
{
  DBG ('{0}: Parameters: {1}' -f $MyInvocation.MyCommand.Name, (($PSBoundParameters.Keys | ? { $_ -ne 'object' } | % { '{0}={1}' -f $_, $PSBoundParameters[$_]}) -join $multivalueSeparator))

  DBGIF $MyInvocation.MyCommand.Name { Is-Null $vm }
  DBGIF $MyInvocation.MyCommand.Name { Is-EmptyString $path }

  if ((Is-NonNull $vm) -and (Is-ValidString $path)) {

    $dvdDrive = Assert-VMxDrive $vm 'dvd' $controller $ctrlID $deviceID

    Mount-VMxDisk $vm $dvdDrive $path 'iso'
  }
}



function global:Attach-VMxHDDs ($vm, [string[]] $disks, [object] $osVersionInfo)
{
  DBG ('{0}: Parameters: {1}' -f $MyInvocation.MyCommand.Name, (($PSBoundParameters.Keys | ? { $_ -ne 'object' } | % { '{0}={1}' -f $_, $PSBoundParameters[$_]}) -join $multivalueSeparator))
  DBGIF $MyInvocation.MyCommand.Name { Is-Null $vm }

  $vmGen = Get-VMxGeneration $vm

  if ((Is-NonNull $vm) -and ((Get-CountSafe $disks) -gt 0)) {

    DBG ('HDDs requested: {0} | {1} | gen = {2}' -f $disks.Count, ($disks -join ', '), $vmGen)

    DBGIF $MyInvocation.MyCommand.Name { Is-Null $osVersionInfo }
    DBGIF $MyInvocation.MyCommand.Name { ($vmGen -gt 1) -and ($osVersionInfo.VersionNumber -lt 6.1) }

    if ($osVersionInfo.versionMM -eq '5.1') {

      DBG ('Attaching HDD to Windows XP which does not support SCSI')
      DBGIF $MyInvocation.MyCommand.Name { $disks.Count -gt 2 }

      if ($disks.Count -ge 1)
      {
        # Note: weird enough but Windows XP setup automatically selects the IDE disk 1
        #       as the installation target instead of correctly determining a partition
        #       with most free space. Regardles of the free space, it installs
        #       in the second, which would normally be the "install-media" disk

        # Note: The problem here is we can either mount the OS disk to ID 1 instead of 0
        #       but then the OS mounts as D: while hte "install-media" disk mounts as C:
        #       So I have resolved the problem with an empty file filler so that we can
        #       keep the order in normal

        $hdDrive = Assert-VMxDrive $vm 'hd' 'ide' 0 0
     
        Mount-VMxDisk $vm $hdDrive $disks[0] 'vhd'
      }

      if ($disks.Count -ge 2) {

        $hdDrive = Assert-VMxDrive $vm 'hd' 'ide' 0 1
     
        Mount-VMxDisk $vm $hdDrive $disks[1] 'vhd'
      }
    
    } else {

      DBG ('Attaching HDD to OS 5.2 or newer which supports SCSI normally')

      if ($vmGen -lt 2) {
        
        if ($disks.Count -ge 1) {

          DBG ('Attach the first disk to IDE')
          $hdDrive = Assert-VMxDrive $vm 'hd' 'ide' 0 0
     
          Mount-VMxDisk $vm $hdDrive $disks[0] 'vhd'
        }
      
        $nextHdd = 1

      } else {

        DBG ('Will attach everything to SCSI as generation 2 does not support IDE')
        $nextHdd = 0
      }
      

      for ($i = $nextHdd; $i -lt $disks.Count; $i ++) {

        if ($i -le 4) {

          $busId = $i - $nextHdd
          $driveId = 0

        } else {

          DBG ('There are more than 4 SCSI disks, we have to attach to a common bus to different LUNs')
          $busId = 3
          $driveId = $i - 3 - $nextHdd
        }

        DBG ('Attach disk to SCSI: #{0}/{1} | {2} | drvId = {3}' -f $i, $busId, $disks[$i], $driveId)
        $scsiDrive = Assert-VMxDrive $vm 'hd' 'scsi' $busId $driveId

        Mount-VMxDisk $vm $scsiDrive $disks[$i] 'vhd'
      }
    }
  }
}


function global:Get-VMxDisks ([wmi] $vm, [bool] $includeVFD, [bool] $includeISO, [bool] $doNotIncludeVHD, [bool] $includeParents, [bool] $idParents)
{
  DBG ('{0}: Parameters: {1}' -f $MyInvocation.MyCommand.Name, (($PSBoundParameters.Keys | ? { $_ -ne 'object' } | % { '{0}={1}' -f $_, $PSBoundParameters[$_]}) -join $multivalueSeparator))
  DBGIF $MyInvocation.MyCommand.Name { Is-Null $vm }
  DBGIF $MyInvocation.MyCommand.Name { $doNotIncludeVHD -and $includeParents }

  DBG ('Determine VHDs for machine: {0} | {1:s} | {2}' -f $vm.ElementName, $vm.ConvertToDateTime($vm.InstallDate), $vm.Name)

  $vmms = Get-VMxMS

  [System.Collections.ArrayList] $vhdList = @()

  if ((Is-NonNull $vmms) -and (Is-NonNull $vm)) {

    $vmSettings = Get-WMIRelated $vm Msvm_VirtualSystemSettingData
    DBGIF $MyInvocation.MyCommand.Name { Is-Null $vmSettings }
    DBGIF $MyInvocation.MyCommand.Name { (Get-CountSafe $vmSettings) -lt 1 }

    DBG ('Current and snapshot settings found: {0}' -f (Get-CountSafe $vmSettings))

    if ((Is-NonNull $vmSettings) -and ((Get-CountSafe $vmSettings) -ge 1)) {

      foreach ($oneSettings in $vmSettings) {

        DBG ('Get VHD images for settings: {0} | {1} | {2} | {3:s} | {4}' -f $oneSettings.SettingType, $oneSettings.VirtualSystemType, $oneSettings.ElementName, $oneSettings.ConvertToDateTime($oneSettings.CreationTime), $oneSettings.Description, $oneSttings.SystemName)
        
        DBGIF $MyInvocation.MyCommand.Name { ($global:virtualizationWmiAPIversion -ne 1) -and (-not (($oneSettings.VirtualSystemType -eq 'Microsoft:Hyper-V:System:Realized') -or ($oneSettings.VirtualSystemType -eq 'Microsoft:Hyper-V:Snapshot:Realized'))) }
        DBGIF $MyInvocation.MyCommand.Name { ($global:virtualizationWmiAPIversion -eq 1) -and (-not (($oneSettings.SettingType -eq 3) -or ($oneSettings.SettingType -eq 5))) }

        [System.Collections.ArrayList] $vhds = @()

        if (-not $doNotIncludeVHD) {

          (Get-WMIRelated $oneSettings $hypervResourceSpecs['storClassVHDVFDISO']) | ? { ($_.ResourceType -eq $hypervResourceSpecs['storResTypeVHDVFDISO']) -and ($_.ResourceSubType -eq $hypervResourceSpecs['storSubTypeVHD']) } | % { [void] $vhds.Add($_) }
        }

        if ($includeVFD) {

          (Get-WMIRelated $oneSettings $hypervResourceSpecs['storClassVHDVFDISO']) | ? { ($_.ResourceType -eq $hypervResourceSpecs['storResTypeVHDVFDISO']) -and ($_.ResourceSubType -eq $hypervResourceSpecs['storSubTypeVFD']) } | % { [void] $vhds.Add($_) }
        }

        if ($includeISO) {

          (Get-WMIRelated $oneSettings $hypervResourceSpecs['storClassVHDVFDISO']) | ? { ($_.ResourceType -eq $hypervResourceSpecs['storResTypeVHDVFDISO']) -and ($_.ResourceSubType -eq $hypervResourceSpecs['storSubTypeISO']) } | % { [void] $vhds.Add($_) }
        }


        DBG ('Found VHD/disk images: {0}' -f (Get-CountSafe $vhds))

        if ((Get-CountSafe $vhds) -gt 0) {

          foreach ($oneVHD in $vhds) {
          
            DBGIF $MyInvocation.MyCommand.Name { (Get-CountSafe $oneVHD."$($hypervResourceSpecs['storPathVHDVFDISO'])") -ne 1 }

            foreach ($oneVHDfile in $oneVHD."$($hypervResourceSpecs['storPathVHDVFDISO'])") {

              $isVHD = Contains-Safe $global:vhdExtensions ([System.IO.Path]::GetExtension($oneVHDfile))
              DBG ('One VHD/disk file: vhd = {0} | {1}' -f $isVHD, $oneVHDfile)
              
              DBGIF $MyInvocation.MyCommand.Name { (Resolve-PathSafe $oneVHDfile) -ne $oneVHDfile }
              DBGIF $MyInvocation.MyCommand.Name { (-not $isVHD) -and ($oneVHDfile -notlike '*.vfd') -and ($oneVHDfile -notlike '*.iso') }
              DBGIF $MyInvocation.MyCommand.Name { $doNotIncludeVHD -and $isVHD }
              DBGIF $MyInvocation.MyCommand.Name { (-not $includeVFD) -and ($oneVHDfile -like '*.vfd') }
              DBGIF $MyInvocation.MyCommand.Name { (-not $includeISO) -and ($oneVHDfile -like '*.iso') }
              DBGIF $MyInvocation.MyCommand.Name { -not (Test-Path $oneVHDfile) }
              
              # Nobody knows why there are duplicate snapshot settings for every snapshot
              #DBGIF $MyInvocation.MyCommand.Name { Contains-Safe $vhdList $oneVHDfile }

              if ($isVHD -and $idParents) {

                Add-ListUnique ([ref] $vhdList) $oneVHDfile

              } else {

                Add-ListUnique ([ref] $vhdList) $oneVHDfile
              }

              if ($includeParents -and $isVHD) {

                DBG ('Will add parent VHDs for differencing disk')

                $vhdInfo = Get-VHDInfo $oneVHDfile
                
                while (($vhdInfo.type -eq 'diff') -and (Is-ValidString $vhdInfo.parent)) {

                  DBGIF $MyInvocation.MyCommand.Name { -not (Test-Path $vhdInfo.parent) }
                  Add-ListUnique ([ref] $vhdList) $vhdInfo.parent

                  $vhdInfo = Get-VHDInfo $vhdInfo.parent
                }
              }
            }
          }
        }
      }
    }
  }

  DBG ('Returning VHD files: {0}' -f (Get-CountSafe $vhdList))
  DBGIF $MyInvocation.MyCommand.Name { (-not $doNotIncludeVHD) -and ((Get-CountSafe $vhdList) -lt 1) }
  DBGIF $MyInvocation.MyCommand.Name { (-not $doNotIncludeVHD) -and ((Get-CountSafe ($vhdList | ? { $_ -notlike '*.vfd' })) -lt 1) }

  return ,$vhdList
}



function global:Get-VMxAllDisksUnique ()
{
  DBG ('{0}: Parameters: {1}' -f $MyInvocation.MyCommand.Name, (($PSBoundParameters.Keys | ? { $_ -ne 'object' } | % { '{0}={1}' -f $_, $PSBoundParameters[$_]}) -join $multivalueSeparator))

  [Collections.ArrayList] $allDisks = @()
  $allVMs = Get-VMxAll
  DBGIF $MyInvocation.MyCommand.Name { (Get-CountSafe $allVMs) -lt 1 }

  if ((Get-CountSafe $allVMs) -gt 0) {

    foreach ($oneVM in $allVMs) {

      $oneVMdisks = Get-VMxDisks $oneVM -includeVFD $true -includeISO $true -includeParents $true
      DBGIF $MyInvocation.MyCommand.Name { (Get-CountSafe $oneVMdisks) -lt 1 }

      if ((Get-CountSafe $oneVMdisks) -gt 0) {

        foreach ($oneVMdisk in $oneVMdisks) {

          Add-ListUnique ([ref] $allDisks) $oneVMdisk
        }
      }
    }
  }

  DBG ('All unique VM disks found: # = {0} | {1}' -f (Get-CountSafe $allDisks), ($allDisks -join ', '))
  DBGIF $MyInvocation.MyCommand.Name { (Get-CountSafe $allDisks) -lt 1 }

  return ,$allDisks
}


function global:Get-VMxBaseFolder ([object] $vm, [bool] $includeVFD)
{
  DBG ('{0}: Parameters: {1}' -f $MyInvocation.MyCommand.Name, (($PSBoundParameters.Keys | ? { $_ -ne 'object' } | % { '{0}={1}' -f $_, $PSBoundParameters[$_]}) -join $multivalueSeparator))
  DBGIF $MyInvocation.MyCommand.Name { Is-Null $vm }
 
  [string] $baseVmFolder = $null

  if (Is-NonNull $vm) {

    $vhdList = Get-VMxDisks $vm $includeVFD

    foreach ($oneVHD in $vhdList) {

      $oneVhdFolder = Split-Path $oneVHD -Parent
      if (Is-EmptyString $baseVmFolder) {

        $baseVmFolder = $oneVhdFolder

      } elseif ($baseVmFolder -ne $oneVhdFolder) {

        DBGIF ('Different base VM folders. Skipping: {0} | {1} | {2}' -f $vm.ElementName, $baseVmFolder, $oneVhdFolder) { $true }
        $baseVmFolder = $null
        break
      }
    }
  }


  DBG ('Base VM going for security checks: {0}' -f $baseVmFolder)

  if ((Is-ValidString $baseVmFolder) -and (Is-EmptyString (Split-Path $baseVmFolder -Parent).Trim())) {

    DBGIF 'VM base folder is drive root. Skipping!' { $true }
    $baseVmFolder = $null
  }

  if ((Is-ValidString $baseVmFolder) -and ((Get-CountSafe (Get-ChildItem $baseVmFolder -Recurse)) -gt 50)) {

    DBGIF 'VM base folder contains more than 50 items. Skipping!' { $true }
    $baseVmFolder = $null
  }

  if ((Is-ValidString $baseVmFolder) -and (($baseVmFolder -like "$($env:SystemDrive)*") -or ($baseVmFolder -like "$($env:windir)*") -or ($baseVmFolder -like "$($env:ProgramFiles)*") -or ($baseVmFolder -like "$($rootDir)*"))) {

    DBGIF 'VM base folder seems to be inside one of protected system folders. Skipping!' { $true }
    $baseVmFolder = $null
  }

  if (Is-ValidString $baseVmFolder) {

    DBG ('Verify that no other disk files are present in the base VM folder: {0}' -f $baseVmFolder)
    DBGSTART
    $otherDisksInBaseVmFolder = $null
    $otherDisksInBaseVmFolder = Get-ChildItem $baseVmFolder -Recurse -Force | ? { ($_.Name -like '*.vhd') -or ($_.Name -like '*.vhdx') -or ($_.Name -like '*.vfd') } | Select -Expand FullName | ? { $vhdList -notcontains $_ }
    DBGER $MyInvocation.MyCommand.Name $error
    DBGEND
    DBGIF ('Found some other VHD or VHDX or VFD files inside the proposed base VM folder: {0} | {1} | #{2} | {3}' -f $vm.ElementName, $baseVmFolder, (Get-CountSafe $otherDisksInBaseVmFolder), ($otherDisksInBaseVmFolder -join ',')) { (Get-CountSafe $otherDisksInBaseVmFolder) -gt 0 }

    if ((Get-CountSafe $otherDisksInBaseVmFolder) -gt 0) {
    
      $baseVmFolder = $null
    }
  }

  if ((Is-ValidString $baseVmFolder) -and (-not (Test-Path $baseVmFolder))) {

    DBGIF ('What base VM folder we found does not exist actually: {0}' -f $baseVmFolder) { $true }
    $baseVmFolder = $null
  }


  DBG ('Base VM folder: {0}' -f $baseVmFolder)
  DBGIF ('Didnt find common sole VM base folder: {0}' -f $vm.ElementName) { (Is-EmptyString $baseVmFolder) }

  return $baseVmFolder    
}


function global:Get-VMxRAM ($vm)
{
  DBG ('{0}: Parameters: {1}' -f $MyInvocation.MyCommand.Name, (($PSBoundParameters.Keys | ? { $_ -ne 'object' } | % { '{0}={1}' -f $_, $PSBoundParameters[$_]}) -join $multivalueSeparator))

  $vmms = Get-VMxMS

  $vmSettings = Get-VMxSettings $vm
  $memory = Get-WMIRelatedSingle $vmSettings "Msvm_MemorySettingData"

  if ($memory.DynamicMemoryEnabled) {

    DBG ('Dynamic memory enabled')
    # virtual quantity = startup RAM
    # reservation = currently assigned amount
    # limit = the upper limit
    $vmRAM = Format-MultiValue @($memory.VirtualQuantity, $memory.Reservation, $memory.Limit)
  
  } else {

    DBG ('Static memory')
    $vmRAM = $memory.VirtualQuantity
  }
  
  DBG ('VM memory: {0} | {1} | {2}' -f $vmRAM, $vm.ElementName, $vm.Name)

  return $vmRAM
}


function global:Set-VMxRAM ($vm, [string] $ram)
{
  DBG ('{0}: Parameters: {1}' -f $MyInvocation.MyCommand.Name, (($PSBoundParameters.Keys | ? { $_ -ne 'object' } | % { '{0}={1}' -f $_, $PSBoundParameters[$_]}) -join $multivalueSeparator))
  DBGIF $MyInvocation.MyCommand.Name { Is-Null $vm }

  if ((Is-NonNull $vm) -and (Is-ValidString $ram) -and ($ram -ne $global:emptyValueMarker)) {

    [int] $ramInitial = 0
    [int] $ramLow = 0
    [int] $ramHi = 0

    $ramSettings = Split-MultiValue $ram

    $ramInitial = $ramSettings[0]
     
    if ((Get-CountSafe $ramSettings) -gt 1) {

      $ramLow = $ramSettings[1]
      $ramHi = $ramSettings[2]
    }

    if ((Is-NonNull $vm) -and ($ramInitial -gt 0)) {

      $vmms = Get-VMxMS

      $vmSettings = Get-VMxSettings $vm
      $memory = Get-WMIRelatedSingle $vmSettings "Msvm_MemorySettingData"

      if (Is-NonNull $memory) {

        DBG ('Current VM RAM settings: ram = {0} | dyn = {1} | res = {2} | lim = {3}' -f $memory.VirtualQuantity, $memory.DynamicMemoryEnabled, $memory.Reservation, $memory.Limit)
       
        $memory.VirtualQuantity = $ramInitial
  
        if ($ramHi -gt 0) {

          $memory.DynamicMemoryEnabled = $true
          $memory.Reservation = $ramLow
          $memory.Limit = $ramHi
        }

        if ($global:virtualizationWmiAPIversion -ne 1) {
         
          DBG ('Fix dynamic memory vs. NUMA error on Windows 2012+')

          $virtualSysSetting = Get-WMIRelatedSingle $vmSettings "Msvm_VirtualSystemSettingData"
          DBG ('Current virtual system settings: virtualNuma = {0}' -f $virtualSysSetting.VirtualNumaEnabled)

          if ($memory.DynamicMemoryEnabled) {

            DBGSTART
            $virtualSysSetting.VirtualNumaEnabled = $false
            DBGER $MyInvocation.MyCommand.Name $error
            DBGEND
          
          } else {

            DBGSTART
            $virtualSysSetting.VirtualNumaEnabled = $true
            DBGER $MyInvocation.MyCommand.Name $error
            DBGEND
          }

          DBG ('Update virtual system settings about the virtual NUMA')
          DBGSTART
          $res = $vmms.ModifySystemSettings($virtualSysSetting.GetText(1))
          DBGER $MyInvocation.MyCommand.Name $error
          DBGEND
          $jobRes = Wait-HyperVJob ([ref] $res)
        }

        Modify-VMxResource $vm $memory
      }
    }
  
  } else {

    DBG ('No RAM value specified')
  }
}


function global:Get-VMxCPUs ($vm)
{
  DBG ('{0}: Parameters: {1}' -f $MyInvocation.MyCommand.Name, (($PSBoundParameters.Keys | ? { $_ -ne 'object' } | % { '{0}={1}' -f $_, $PSBoundParameters[$_]}) -join $multivalueSeparator))

  $vmSettings = Get-VMxSettings $vm
  $cpu = Get-WMIRelatedSingle $vmSettings "Msvm_ProcessorSettingData"

  if (Is-NonNull $cpu) {

    DBG ('VM CPUs: {0} | {1} | {2} | nestedVMs = {3}' -f $cpu.VirtualQuantity, $vm.ElementName, $vm.Name, $cpu.ExposeVirtualizationExtensions)
  }

  return $cpu.VirtualQuantity
}


function global:Get-VMxNestedVMs ($vm)
{
  DBG ('{0}: Parameters: {1}' -f $MyInvocation.MyCommand.Name, (($PSBoundParameters.Keys | ? { $_ -ne 'object' } | % { '{0}={1}' -f $_, $PSBoundParameters[$_]}) -join $multivalueSeparator))

  $vmSettings = Get-VMxSettings $vm
  $cpu = Get-WMIRelatedSingle $vmSettings "Msvm_ProcessorSettingData"

  [bool] $nestedVMs = $false
  if (Is-NonNull $cpu) {

    $nestedVMs = Parse-BoolSafe $cpu.ExposeVirtualizationExtensions 
    DBG ('VM CPUs: {0} | {1} | {2} | nestedVMs = {3}' -f $cpu.VirtualQuantity, $vm.ElementName, $vm.Name, $nestedVMs)
  }

  return $nestedVMs
}


function global:Get-VMxGeneration ($vm)
{
  DBG ('{0}: Parameters: {1}' -f $MyInvocation.MyCommand.Name, (($PSBoundParameters.Keys | ? { $_ -ne 'object' } | % { '{0}={1}' -f $_, $PSBoundParameters[$_]}) -join $multivalueSeparator))

  $vmSettings = Get-VMxSettings $vm

  if ($vmSettings.VirtualSystemSubType -eq 'Microsoft:Hyper-V:SubType:1') {
  
    return 1
  
  } elseif ($vmSettings.VirtualSystemSubType -eq 'Microsoft:Hyper-V:SubType:2') {

    return 2
  
  } else {

    DBGIF ('Weird VM generation: {0} | {1}' -f $vmSettings.VirtualSystemSubType, $vmSettings.VirtualSystemIdentifier) { $true }
  }
}

function global:Get-VMxSecureBoot ($vm)
{
  DBG ('{0}: Parameters: {1}' -f $MyInvocation.MyCommand.Name, (($PSBoundParameters.Keys | ? { $_ -ne 'object' } | % { '{0}={1}' -f $_, $PSBoundParameters[$_]}) -join $multivalueSeparator))

  $vmSettings = Get-VMxSettings $vm

  if ($vmSettings.VirtualSystemSubType -eq 'Microsoft:Hyper-V:SubType:2') {

    return $vmSettings.SecureBootEnabled
  
  } else {

    DBGIF $MyInvocation.MyCommand.Name { Parse-BoolSafe ([string] $vmSettings.SecureBootEnabled) }
    return $false
  }
}


function global:Get-VMxTpm ($vm)
{
  DBG ('{0}: Parameters: {1}' -f $MyInvocation.MyCommand.Name, (($PSBoundParameters.Keys | ? { $_ -ne 'object' } | % { '{0}={1}' -f $_, $PSBoundParameters[$_]}) -join $multivalueSeparator))

  $vmSettings = Get-VMxSettings $vm

  if (($global:thisOsVersionNumber -ge 10) -and ($vmSettings.VirtualSystemSubType -eq 'Microsoft:Hyper-V:SubType:2')) {

    $securitySettings = Get-WMIRelatedSingle $vmSettings 'Msvm_SecuritySettingData'
    return $securitySettings.TpmEnabled
  
  } else {

    return $false
  }
}


function global:Set-VMxTPM ($vm, [byte[]] $keyProtectorRawData, [bool] $enable)
{
  DBG ('{0}: Parameters: {1}' -f $MyInvocation.MyCommand.Name, (($PSBoundParameters.Keys | ? { $_ -ne 'object' } | % { '{0}={1}' -f $_, $PSBoundParameters[$_]}) -join $multivalueSeparator))

  $vmSettings = Get-VMxSettings $vm
  DBGIF $MyInvocation.MyCommand.Name { $vmSettings.VirtualSystemSubType -ne 'Microsoft:Hyper-V:SubType:2' }

  $securitySettings = Get-WMIRelatedSingle $vmSettings 'Msvm_SecuritySettingData'
  DBGIF $MyInvocation.MyCommand.Name { Is-Null $securitySettings }

  DBG ('Enable/disable TPM within the settings: {0}' -f $enable)
  DBGSTART
  $securitySettings.TpmEnabled = $enable
  DBGER $MyInvocation.MyCommand.Name $error
  DBGEND

  $vmsecs = Get-VMxSecS
  
  DBG ('Apply the key protector to the VM')
  DBGSTART
  $wmiRs = $vmsecs.SetKeyProtector($securitySettings.GetText(1), $keyProtectorRawData)
  DBGER $MyInvocation.MyCommand.Name $error
  DBGEND
  $jobRes = Wait-HyperVJob ([ref] $wmiRs)

  DBG ('Apply the new security settings')
  DBGSTART
  $wmiRs = $vmsecs.ModifySecuritySettings($securitySettings.GetText(1))
  DBGER $MyInvocation.MyCommand.Name $error
  DBGEND
  $jobRes = Wait-HyperVJob ([ref] $wmiRs)
}


function global:Set-VMxCPUs ($vm, [string] $cpus, [string] $vmOS)
# $vmOS must in format such as '5.2clt' or '6.0srv'
{
  DBG ('{0}: Parameters: {1}' -f $MyInvocation.MyCommand.Name, (($PSBoundParameters.Keys | ? { $_ -ne 'object' } | % { '{0}={1}' -f $_, $PSBoundParameters[$_]}) -join $multivalueSeparator))
  DBGIF $MyInvocation.MyCommand.Name { Is-Null $vm }

  if ((Is-NonNull $vm) -and (Is-ValidString $cpus) -and ($cpus -ne $global:emptyValueMarker)) {

    [int] $cpuNo = 0

    DBGIF $MyInvocation.MyCommand.Name { ($cpus -eq 'max') -and (Is-EmptyString $vmOS) }

    if ($cpus -eq 'max') {

      DBG ('Maximum allowed processors requested')
      
      $cpuSocketNo = Get-WMIValue '.' 'SELECT * FROM Win32_ComputerSystem' NumberOfProcessors
      $cpuLogicalNo = Get-WMIValue '.' 'SELECT * FROM Win32_ComputerSystem' NumberOfLogicalProcessors
      $cpuSockets = Get-WMIQueryArray '.' 'SELECT * FROM Win32_Processor'

      $i = 0; [int[]] $cpuCoreMatrix = @(); $cpuSockets | % { $cpuCoreMatrix += @($_.NumberOfCores) ; $i ++ }
    
      DBG ('CPU characteristics: {0} | {1} | {2}' -f $cpuSocketNo, $cpuLogicalNo, ($cpuCoreMatrix -join ','))
      
      DBGIF $MyInvocation.MyCommand.Name { -not (Contains-Safe $global:hypervClientCPUMatrix.Keys $global:thisOSVersionNormal) }
      DBGIF $MyInvocation.MyCommand.Name { (Contains-Safe $global:hypervClientCPUMatrix.Keys $global:thisOSVersionNormal) -and (-not (Contains-Safe $global:hypervClientCPUMatrix[$global:thisOSVersionNormal].Keys $vmOS)) }
      DBGSTART
      [int] $maximumSupportedCPUs = $global:hypervClientCPUMatrix[$global:thisOSVersionNormal][$vmOS]
      DBGER $MyInvocation.MyCommand.Name $error
      DBGEND
      DBG ('Maximum supported guest CPUs for current system: thisOS = {0} | vmOS = {1} | maxCPUs = {2}' -f $global:thisOSVersionNormal, $vmOS, $maximumSupportedCPUs)
      
      DBGIF 'Unsupported guest OS detected' { $maximumSupportedCPUs -lt 1 }
      if ($maximumSupportedCPUs -lt 1) { 

        DBG ('Defaulting to 1 CPU for unsupported OS')
        $maximumSupportedCPUs = 1
      }

      $cpuNo = [Math]::Min($maximumSupportedCPUs, $cpuLogicalNo)
      DBG ('Maximum result CPUs for the guest: {0}' -f $cpuNo)

    } else {

      DBGSTART
      $cpuNo = [int] $cpus
      DBGER $MyInvocation.MyCommand.Name $error
      DBGEND
    }
  
    if ((Is-NonNull $vm) -and ($cpuNo -gt 0)) {

      $vmms = Get-VMxMS

      $vmSettings = Get-VMxSettings $vm
      $cpu = Get-WMIRelatedSingle $vmSettings "Msvm_ProcessorSettingData"

      if (Is-NonNull $cpu) {
  
        DBG ('Current CPUs: cpus = {0} | maxNumaNodes = {1} | maxCPUsOnNuma = {2}' -f $cpu.VirtualQuantity, $cpu.MaxNumaNodesPerSocket, $cpu.MaxProcessorsPerNumaNode)
        DBG ('Set CPUs to be: {0}' -f $cpuNo)

        DBGSTART
        $cpu.VirtualQuantity = $cpuNo
        DBGER $MyInvocation.MyCommand.Name $error
        DBGEND

          <# Just a first trial to overcome the DynamicRAM vs. NUMA problem on APIv2
          DBGSTART
          $cpu.MaxNumaNodesPerSocket = $cpu.MaxNumaNodesPerSocket
          $cpu.MaxProcessorsPerNumaNode = $cpu.MaxProcessorsPerNumaNode
          DBGER $MyInvocation.MyCommand.Name $error
          DBGEND#>
    
        Modify-VMxResource $vm $cpu
      }
    }
  }
}


function global:Set-VMxNestedVMs ($vm, [bool] $enable)
{
  DBG ('{0}: Parameters: {1}' -f $MyInvocation.MyCommand.Name, (($PSBoundParameters.Keys | ? { $_ -ne 'object' } | % { '{0}={1}' -f $_, $PSBoundParameters[$_]}) -join $multivalueSeparator))

  DBGIF $MyInvocation.MyCommand.Name { Is-Null $vm }
  DBGIF $MyInvocation.MyCommand.Name { $global:thisOSVersionNumber -lt 10 }

  if ((Is-NonNull $vm) -and ($global:thisOSVersionNumber -ge 10)) {

    $vmms = Get-VMxMS

    $vmSettings = Get-VMxSettings $vm
    $cpu = Get-WMIRelatedSingle $vmSettings "Msvm_ProcessorSettingData"

    DBG ('Nested VMs: current = {0} | enable = {1}' -f $cpu.ExposeVirtualizationExtensions, $enable)

    DBGSTART
    #$cpu.HideHypervisorPresent = $enable
    $cpu.ExposeVirtualizationExtensions = $enable
    DBGER $MyInvocation.MyCommand.Name $error
    DBGEND

    Modify-VMxResource $vm $cpu
  }
}


function global:Get-VMxShutdownAction ($vm)
{
  DBG ('{0}: Parameters: {1}' -f $MyInvocation.MyCommand.Name, (($PSBoundParameters.Keys | ? { $_ -ne 'object' } | % { '{0}={1}' -f $_, $PSBoundParameters[$_]}) -join $multivalueSeparator))
  DBGIF $MyInvocation.MyCommand.Name { Is-Null $vm }

  [string] $res = $null

  if (Is-NonNull $vm) {

    if ($global:virtualizationWmiAPIversion -eq 1) {

      $settings = Get-VMxSettings $vm -global #Get-WMIRelatedSingle $vm Msvm_VirtualSystemGlobalSettingData

      switch ($settings.AutomaticShutdownAction) 
      {
        0 { $res = 'turnoff' }
        1 { $res = 'save' }
        2 { $res = 'shutdown' }
        default { DBGIF ('Invalid action: {0}' -f $settings.AutomaticShutdownAction) { $true } }
      }
  
      DBG ('Automatic shutdown action: {0} | {1}' -f $settings.AutomaticShutdownAction, $res)

    } else {

      $settings = Get-VMxSettings $vm #Get-WMIRelatedSingle $vm Msvm_VirtualSystemSettingData

      switch ($settings.AutomaticShutdownAction) 
      {
        2 { $res = 'turnoff' }
        3 { $res = 'save' }
        4 { $res = 'shutdown' }
        default { DBGIF ('Invalid action: {0}' -f $settings.AutomaticShutdownAction) { $true } }
      }
  
      DBG ('Automatic shutdown action: {0} | {1}' -f $settings.AutomaticShutdownAction, $res)
    }
  }

  return $res
}


function global:Get-VMxStartupAction ($vm)
{
  DBG ('{0}: Parameters: {1}' -f $MyInvocation.MyCommand.Name, (($PSBoundParameters.Keys | ? { $_ -ne 'object' } | % { '{0}={1}' -f $_, $PSBoundParameters[$_]}) -join $multivalueSeparator))
  DBGIF $MyInvocation.MyCommand.Name { Is-Null $vm }

  [string] $res = $null

  if (Is-NonNull $vm) {

    if ($global:virtualizationWmiAPIversion -eq 1) {

      $settings = Get-VMxSettings $vm -global #Get-WMIRelatedSingle $vm Msvm_VirtualSystemGlobalSettingData

      switch ($settings.AutomaticStartupAction) 
      {
        0 { $res = 'none' }
        1 { $res = 'previous' }
        2 { $res = 'always' }
        default { DBGIF ('Invalid action: {0}' -f $settings.AutomaticStartupAction) { $true } }
      }
  
      DBG ('Automatic startup action: {0} | {1}' -f $settings.AutomaticStartupAction, $res)

    } else {

      $settings = Get-VMxSettings $vm #Get-WMIRelatedSingle $vm Msvm_VirtualSystemSettingData

      switch ($settings.AutomaticStartupAction) 
      {
        2 { $res = 'none' }
        3 { $res = 'previous' }
        4 { $res = 'always' }
        default { DBGIF ('Invalid action: {0}' -f $settings.AutomaticStartupAction) { $true } }
      }
  
      DBG ('Automatic startup action: {0} | {1}' -f $settings.AutomaticStartupAction, $res)
    }
  }

  return $res
}


function global:Get-VMxStartupDelay ($vm)
{
  DBG ('{0}: Parameters: {1}' -f $MyInvocation.MyCommand.Name, (($PSBoundParameters.Keys | ? { $_ -ne 'object' } | % { '{0}={1}' -f $_, $PSBoundParameters[$_]}) -join $multivalueSeparator))
  DBGIF $MyInvocation.MyCommand.Name { Is-Null $vm }

  [string] $res = $null

  if (Is-NonNull $vm) {

    if ($global:virtualizationWmiAPIversion -eq 1) {

      $settings = Get-VMxSettings $vm -global #Get-WMIRelatedSingle $vm Msvm_VirtualSystemGlobalSettingData

      $res = [System.Management.ManagementDateTimeconverter]::ToTimeSpan($settings.AutomaticStartupActionDelay).TotalSeconds
      DBG ('Automatic startup action delay: {0} | {1}sec' -f $settings.AutomaticStartupActionDelay, $res)

    } else {

      $settings = Get-VMxSettings $vm #Get-WMIRelatedSingle $vm Msvm_VirtualSystemSettingData

      $res = [System.Management.ManagementDateTimeconverter]::ToTimeSpan($settings.AutomaticStartupActionDelay).TotalSeconds
      DBG ('Automatic startup action delay: {0} | {1}sec' -f $settings.AutomaticStartupActionDelay, $res)
    }
  }

  return $res
}


function global:Get-VMxNumLock ($vm)
{
  DBG ('{0}: Parameters: {1}' -f $MyInvocation.MyCommand.Name, (($PSBoundParameters.Keys | ? { $_ -ne 'object' } | % { '{0}={1}' -f $_, $PSBoundParameters[$_]}) -join $multivalueSeparator))

  $vmSettings = Get-VMxSettings $vm

  DBG ('VM numlock state: {0} | {1} | {2}' -f $vmSettings.BIOSNumLock, $vm.ElementName, $vm.Name)

  return $vmSettings.BIOSNumLock
}


function global:Set-VMxNumLock ($vm, [bool] $numlock)
{
  DBG ('{0}: Parameters: {1}' -f $MyInvocation.MyCommand.Name, (($PSBoundParameters.Keys | ? { $_ -ne 'object' } | % { '{0}={1}' -f $_, $PSBoundParameters[$_]}) -join $multivalueSeparator))

  $vmSettings = Get-VMxSettings $vm

  DBG ('Current numlock setting: {0}' -f $vmSettings.BIOSNumLock)
  DBG ('Should enable numlock: {0}' -f $numlock)

  $vmSettings.BIOSNumLock = $numlock

  $vmms = Get-VMxMS
  if ($global:virtualizationWmiAPIversion -eq 1) {

    DBG ('Save VM settings with APIv1')
    DBGSTART
    $res = $vmms.ModifyVirtualSystem($vm, $vmSettings.GetText(1))
    DBGER $MyInvocation.MyCommand.Name $error
    DBGEND
    $jobRes = Wait-HyperVJob ([ref] $res)
  
  } else {

    DBG ('Save VM settings with APIv2')
    DBGSTART
    $res = $vmms.ModifySystemSettings($vmSettings.GetText(1))
    DBGER $MyInvocation.MyCommand.Name $error
    DBGEND
    $jobRes = Wait-HyperVJob ([ref] $res)
  }
}

function global:Get-VMxHeartbeat ($vm)
{
  DBG ('{0}: Parameters: {1}' -f $MyInvocation.MyCommand.Name, (($PSBoundParameters.Keys | ? { $_ -ne 'object' } | % { '{0}={1}' -f $_, $PSBoundParameters[$_]}) -join $multivalueSeparator))

  $vmms = Get-VMxMS

  $vmSettings = Get-VMxSettings $vm
  $hbSetting = (Get-WMIRelated $vmSettings Msvm_HeartbeatComponentSettingData) | select -f 1

  DBG ('VM heartbeating setting: {0} | {1} | {2}' -f $hbSetting.EnabledState, $vm.ElementName, $vm.Name)

  return ($hbSetting.EnabledState -eq 2)
}

function global:Get-VMxHeartbeatStatus ($vm, [bool] $noAssertFailure)
{
  DBG ('{0}: Parameters: {1}' -f $MyInvocation.MyCommand.Name, (($PSBoundParameters.Keys | ? { $_ -ne 'object' } | % { '{0}={1}' -f $_, $PSBoundParameters[$_]}) -join $multivalueSeparator))

  $okStatus = $false

  $vmms = Get-VMxMS

  $hbStatus = (Get-WMIRelated $vm Msvm_HeartbeatComponent) | select -f 1
  DBGIF 'The heartbeat integration component is NOT ENABLED or machine NOT RUNNING' { (Is-Null $hbStatus) -and (-not $noAssertFailure) }
  
  if (Is-NonNull $hbStatus) {

    [UInt16[]] $hbStatusValues = $hbStatus.OperationalStatus
    DBGIF 'The heartbeat integration component is NOT RUNNING inside a running VM' { ($hbStatusValues.Length -ne 2) -and (-not $noAssertFailure) }
    
    if ($hbStatusValues.Length -eq 2) {

      $machineHbStatus = $hbStatusValues[0]
      $okStatus = $machineHbStatus -eq 2

      DBG ('VM heartbeat status: ok = {0} | value = {1} | {2} | {3}' -f $okStatus, $machineHbStatus, $vm.ElementName, $vm.Name)
    }
  }

  return $okStatus
}

function global:Get-VMxTimeSync ($vm)
{
  DBG ('{0}: Parameters: {1}' -f $MyInvocation.MyCommand.Name, (($PSBoundParameters.Keys | ? { $_ -ne 'object' } | % { '{0}={1}' -f $_, $PSBoundParameters[$_]}) -join $multivalueSeparator))

  $vmms = Get-VMxMS

  $vmSettings = Get-VMxSettings $vm
  $time = (Get-WMIRelated $vmSettings "Msvm_TimeSyncComponentSettingData") | select -f 1

  DBG ('VM time sync setting: {0} | {1} | {2}' -f $time.EnabledState, $vm.ElementName, $vm.Name)
  
  return ($time.EnabledState -eq 2)
}


function global:Set-VMxTimeSync ($vm, [bool] $syncTime )
{
  DBG ('{0}: Parameters: {1}' -f $MyInvocation.MyCommand.Name, (($PSBoundParameters.Keys | ? { $_ -ne 'object' } | % { '{0}={1}' -f $_, $PSBoundParameters[$_]}) -join $multivalueSeparator))

  $vmms = Get-VMxMS

  $vmSettings = Get-VMxSettings $vm
  $time = (Get-WMIRelated $vmSettings "Msvm_TimeSyncComponentSettingData") | select -f 1
  
  if ($syncTime) {

    $time.EnabledState = 2

  } else {

    $time.EnabledState = 3
  }
  
  Modify-VMxResource $vm $time
}


function global:Set-VMxHostESM ([bool] $enable)
{
  DBG ('{0}: Parameters: {1}' -f $MyInvocation.MyCommand.Name, (($PSBoundParameters.Keys | ? { $_ -ne 'object' } | % { '{0}={1}' -f $_, $PSBoundParameters[$_]}) -join $multivalueSeparator))

  DBGIF $MyInvocation.MyCommand.Name { $global:thisOSVersionNumber -lt 6.3 }

  $vmms = Get-VMxMS
  $msSettings = Get-VMxMSSettings

  DBG ('Enable the enhanced session mode: {0}' -f $enable)
  DBGSTART
  $msSettings.EnhancedSessionModeEnabled = $enable
  DBGER $MyInvocation.MyCommand.Name $error
  DBGEND

  DBG ('Configure the service settings')
  DBGSTART
  $res = $vmms.ModifyServiceSettings($msSettings.GetText(1))
  DBGER $MyInvocation.MyCommand.Name $error
  DBGEND
  $jobRes = Wait-HyperVJob ([ref] $res)

  $updatedMSSettings = Get-VMxMSSettings
  DBGIF $MyInvocation.MyCommand.Name { $updatedMSSettings.EnhancedSessionModeEnabled -ne $enable }
}


function global:Get-VMxSwitches ()
{
  DBG ('{0}: Parameters: {1}' -f $MyInvocation.MyCommand.Name, (($PSBoundParameters.Keys | ? { $_ -ne 'object' } | % { '{0}={1}' -f $_, $PSBoundParameters[$_]}) -join $multivalueSeparator))

  $switches = Get-WmiQueryArray '.' ('SELECT * FROM {0}' -f $global:hypervResourceSpecs['ntwSwitch']) -namespace $global:virtualizationNamespace

  return ,$switches
}


function global:Get-VMxSwitch ([string] $name)
{
  DBG ('{0}: Parameters: {1}' -f $MyInvocation.MyCommand.Name, (($PSBoundParameters.Keys | ? { $_ -ne 'object' } | % { '{0}={1}' -f $_, $PSBoundParameters[$_]}) -join $multivalueSeparator))
  DBGIF $MyInvocation.MyCommand.Name { Is-EmptyString $name }

  [WMI] $switch = $null

  if (Is-ValidString $name) {

    $switch = Get-WmiQuerySingleObject '.' ('SELECT * FROM {0} WHERE ElementName = "{1}"' -f $global:hypervResourceSpecs['ntwSwitch'], $name) -namespace $global:virtualizationNamespace
    DBGIF $MyInvocation.MyCommand.Name { Is-Null $switch }
  }

  return $switch
}


function global:Disconnect-VMxsFromSwitch ([string] $name, [string[]] $vmNames, [bool] $allVMs, [bool] $managementOS)
{
  DBG ('{0}: Parameters: {1}' -f $MyInvocation.MyCommand.Name, (($PSBoundParameters.Keys | ? { $_ -ne 'object' } | % { '{0}={1}' -f $_, $PSBoundParameters[$_]}) -join $multivalueSeparator))
  DBGIF $MyInvocation.MyCommand.Name { Is-EmptyString $name }
  DBGIF $MyInvocation.MyCommand.Name { ((Get-CountSafe $vmNames) -lt 1) -and (-not $allVMs) }
  #DBGIF $MyInvocation.MyCommand.Name { $managementOS -and ($global:virtualizationWmiAPIversion -ne 1) }

  if (Is-ValidString $name) {
  
    $switch = Get-VMxSwitch $name
    
    if (Is-NonNull $switch) {

      $vmsms = Get-VMxSMS
      $vmms = Get-VMxMS

      if ($global:virtualizationWmiAPIversion -eq 1) {

        DBG ('Get all connected VMs with API1')
        $switchPorts = Get-WmiRelated $switch $global:hypervResourceSpecs['ntwSwitchPort']
        DBG ('Found connected switch ports: {0}' -f (Get-CountSafe $switchPorts))

        if ((Get-CountSafe $switchPorts) -gt 0) {

          foreach ($oneSwitchPort in $switchPorts) {

            DBG ('One switch port: {0} | {1}' -f $oneSwitchPort.__CLASS, $oneSwitchPort.Name)

            # Note: there is either Msvm_VmLANEndpoint if the switch port is the PRIVATE port
            #       or there is Msvm_SwitchLANEndpoint if the switch port is either INTERNAL or EXTERNAL
            #       Thus the Msvm_VmLANEndpoint may not be present at all on EXTERNAL/INTERNAL
            #       switches
            $vmLanEndpoints = Get-WmiRelated $oneSwitchPort 'Msvm_VmLANEndpoint'
            DBGIF $MyInvocation.MyCommand.Name { (Get-CountSafe $vmLanEndpoints) -gt 1 }
            DBG ('The port has VMs connected: {0}' -f (Get-CountSafe $vmLanEndpoints))

            if ((Get-CountSafe $vmLanEndpoints) -gt 0) {
            
              foreach ($oneVmLanEndpoint in $vmLanEndpoints) {

                $synthEthPort = Get-WmiRelatedSingle $oneVmLanEndpoint 'Msvm_SyntheticEthernetPort','Msvm_EmulatedEthernetPort'

                if (Is-NonNull $synthEthPort) {

                  $vm = Get-WmiRelatedSingle $synthEthPort $global:hypervResourceSpecs['computerSystem']

                  if (Is-NonNull $vm) {
                   
                    DBG ('Virtual machine connected to the port: {0} | {1}' -f $vm.ElementName, $vm.Name)

                    if ($allVMs -or (Contains-Safe $vmNames $vm.ElementName)) {
                        
                      DBG ('Going to disconnect the VM from the switch')
                      DBGSTART
                      $res = $vmsms.DisconnectSwitchPort($oneSwitchPort)
                      DBGER $MyInvocation.MyCommand.Name $error
                      DBGEND
                      $jobRes = Wait-HyperVJob ([ref] $res)

                      DBG ('Going to delete the orphaned switch port')
                      DBGSTART
                      $res = $vmsms.DeleteSwitchPort($oneSwitchPort)
                      DBGER $MyInvocation.MyCommand.Name $error
                      DBGEND
                      $jobRes = Wait-HyperVJob ([ref] $res)
                    }
                  }
                }
              }
            
            } else {

              DBG ('No VMs connected to the switch port')

              if ($managementOS) {

                DBG ('Test if the switch port has some EXTERNAL/INTERNAL NICs connected')
                $switchLanEndpoints = Get-WmiRelated $oneSwitchPort 'Msvm_SwitchLANEndpoint'
                DBGIF $MyInvocation.MyCommand.Name { (Get-CountSafe $switchLanEndpoints) -gt 1 }
                DBG ('The switch port has EXTERNAL/INTERNAL NICs connected: {0}' -f (Get-CountSafe $switchLanEndpoints))

                if ((Get-CountSafe $switchLanEndpoints) -gt 0) {

                  foreach ($oneSwitchLANEndpoint in $switchLanEndpoints) {

                    $internalOrExternalPort = Get-WmiRelatedSingle $oneSwitchLANEndpoint 'Msvm_InternalEthernetPort', 'Msvm_ExternalEthernetPort'
                    DBG ('Found INTERNAL or EXTERNAL port to be deleted: {0}' -f $internalOrExternalPort.__CLASS)

                    if (Is-NonNull $internalOrExternalPort) {

                      if ($internalOrExternalPort.__CLASS -eq 'Msvm_InternalEthernetPort') {

                        DBG ('Going to delete the ManagementOS INTERNAL NIC from the switch')
                        DBGSTART
                        $res = $vmsms.DeleteInternalEthernetPort($internalOrExternalPort)
                        DBGER $MyInvocation.MyCommand.Name $error
                        DBGEND
                        $jobRes = Wait-HyperVJob ([ref] $res)
                    
                      } else {

                        DBGIF $MyInvocation.MyCommand.Name { $internalOrExternalPort.__CLASS -ne 'Msvm_ExternalEthernetPort' }

                        DBG ('Going to delete the ManagementOS EXTERNAL NIC from the switch')
                        DBGSTART
                        $res = $vmsms.UnbindExternalEthernetPort($internalOrExternalPort)
                        DBGER $MyInvocation.MyCommand.Name $error
                        DBGEND
                        $jobRes = Wait-HyperVJob ([ref] $res)
                      }
                    }
                  }
                }
              }
            }
          }
        }        
        
      } else { # Note: APIv2

        [string[]] $internalExternalPorts = @()

        DBG ('Get all connected VMs with API2')
        $switchPorts = Get-WmiRelated $switch $global:hypervResourceSpecs['ntwSwitchPort']
        DBG ('Found connected switch ports: {0}' -f (Get-CountSafe $switchPorts))

        if ((Get-CountSafe $switchPorts) -gt 0) {

          foreach ($oneSwitchPort in $switchPorts) {

            DBG ('One switch port: {0} | {1}' -f $oneSwitchPort.__CLASS, $oneSwitchPort.Name)
            $epASD = Get-WmiRelated $oneSwitchPort 'Msvm_EthernetPortAllocationSettingData'
            DBGIF $MyInvocation.MyCommand.Name { (Get-CountSafe $epASD) -gt 1 }

            if ((Get-CountSafe $epASD) -gt 0) {

              foreach ($oneEPASD in $epASD) {

                DBGIF $MyInvocation.MyCommand.Name { (Get-CountSafe $oneEPASD.HostResource) -ne 1 }

                $mgmtPath = [System.Management.ManagementPath] $oneEPASD.HostResource[0]
                DBG ('Host resource from the settings: {0}' -f $mgmtPath.Path)
                DBG ('Host resource class from the settings: {0}' -f $mgmtPath.ClassName)

                if ($mgmtPath.ClassName -eq $global:hypervResourceSpecs['computerSystem']) {

                  DBG ('This port is INTERNAL')
                  if ($managementOS) {

                    DBG ('Will remove the port as part of a single operation later')
                    $internalExternalPorts += $oneEPASD.__PATH
                  }

                } elseif ($mgmtPath.ClassName -eq $global:hypervResourceSpecs['ntwSwitchPortExternal']) {

                  DBG ('This port is EXTERNAL')
                  if ($managementOS) {

                    $internalExternalPorts += $oneEPASD.__PATH
                  }

                } else {

                  DBG ('This port is VIRTUAL. Proceed to disconnect')

                  $vsSD = Get-WmiRelatedSingle $oneEPASD 'Msvm_VirtualSystemSettingData'

                  if (Is-NonNull $vsSD) {

                    $vm = Get-WmiRelatedSingle $vsSD $global:hypervResourceSpecs['computerSystem']
                  
                    if (Is-NonNull $vm) {

                      DBG ('Virtual machine connected to the port: {0} | {1}' -f $vm.ElementName, $vm.Name)

                      if ($allVMs -or (Contains-Safe $vmNames $vm.ElementName)) {
                        
                        DBG ('Going to disconnect the VM from the switch')
                        DBGSTART
                        $oneEPASD.EnabledState = 3;
                        $res = $vmms.ModifyResourceSettings($oneEPASD.GetText(1))
                        DBGER $MyInvocation.MyCommand.Name $error
                        DBGEND
                        $jobRes = Wait-HyperVJob ([ref] $res)

                        <#
                        # Note: This would be a method to delete the INTERNAL/EXTERNAL switch ports
                        #       making the switch either INTERNAL only or even PRIVATE only
                        DBG ('Going to delete the switch port: {0}' -f $oneEPASD.__PATH)
                        DBGSTART
                        $res = $vmsms.RemoveResourceSettings(@($oneEPASD.__PATH))
                        DBGER $MyInvocation.MyCommand.Name $error
                        DBGEND
                        $jobRes = Wait-HyperVJob ([ref] $res)
                        #>
                      }
                    }
                  }
                }
              }
            }
          }
        }

        if (($managementOS) -and ((Get-CountSafe $internalExternalPorts) -gt 0)) {

          DBG ('Going to delete the INTERNAL/EXTERNAL switch ports: {0} | {1}' -f (Get-CountSafe $internalExternalPorts), ($internalExternalPorts -join ' ;; '))
          DBGSTART
          $res = $vmsms.RemoveResourceSettings($internalExternalPorts)
          DBGER $MyInvocation.MyCommand.Name $error
          DBGEND
          $jobRes = Wait-HyperVJob ([ref] $res)
        }
      }
    }
  } 
}


function global:Delete-VMxSwitch ([string] $name, [bool] $disconnectAllVMs)
{
  DBG ('{0}: Parameters: {1}' -f $MyInvocation.MyCommand.Name, (($PSBoundParameters.Keys | ? { $_ -ne 'object' } | % { '{0}={1}' -f $_, $PSBoundParameters[$_]}) -join $multivalueSeparator))
  DBGIF $MyInvocation.MyCommand.Name { Is-EmptyString $name }

  DBGIF ('Must not delete PERMANENT-* VM switch: {0}' -f $name) { $name -like $global:hypervPermanent }

  if ((Is-ValidString $name) -and ($name -notlike $global:hypervPermanent)) {

    $switch = Get-VMxSwitch $name

    if (Is-NonNull $switch) {

      if ($disconnectAllVMs) {

        DBG ('Disconnect all VMs first')      
        Disconnect-VMxsFromSwitch $name -allVMs $true -managementOS $true
      }


      $vmsms = Get-VMxSMS

      if ($global:virtualizationWmiAPIversion -eq 1) {

        DBG ('Delete the switch with APIv1: {0} | {1}' -f $name, $switch.Name)
        DBGSTART 
        $res = $vmsms.DeleteSwitch($switch)
        DBGER $MyInvocation.MyCommand.Name $error
        DBGEND
        $jobRes = Wait-HyperVJob ([ref] $res)
      
      } else {

        DBG ('Delete the switch with APIv2: {0}' -f $name)
        DBGSTART
        $res = $vmsms.DestroySystem($switch)
        DBGER $MyInvocation.MyCommand.Name $error
        DBGEND
        $jobRes = Wait-HyperVJob ([ref] $res)
      }
    }
  }
}


function global:Add-VMxSwitchPort ([WMI] $switch)
{
  DBG ('{0}: Parameters: {1}' -f $MyInvocation.MyCommand.Name, (($PSBoundParameters.Keys | ? { $_ -ne 'object' } | % { '{0}={1}' -f $_, $PSBoundParameters[$_]}) -join $multivalueSeparator))

  [WMI] $newPort = $null

  if ($global:virtualizationWmiAPIversion -eq 1) {

    if (Is-NonNull $switch) {

      DBGIF $MyInvocation.MyCommand.Name { Is-EmptyString $switch.ElementName }

      $nicSvc = Get-VMxSMS

      $nicPortName = '{0}-Port-{1}' -f $switch.ElementName, (Get-GuidString)
      DBG ('Create switch port with APIv1: {0} | {1}' -f $switch.ElementName, $nicPortName)

      DBGSTART
      $res = $null
      $res = $nicSvc.CreateSwitchPort($switch, $nicPortName, $nicPortName)
      DBGER $MyInvocation.MyCommand.Name $error
      DBGEND
      $jobRes = Wait-HyperVJob ([ref] $res)

      DBGSTART
      $newPort = $res.CreatedSwitchPort
      DBGER $MyInvocation.MyCommand.Name $error
      DBGEND

      DBGIF $MyInvocation.MyCommand.Name { $newPort.Description -ne 'Microsoft Virtual Switch Port' }
    }

  } else {

    DBG ('Create switch port with APIv2')
    #$portSettings = Spawn-WMIInstance '.' Msvm_EthernetPortAllocationSettingData $global:virtualizationNamespace

    # 33, Microsoft:Hyper-V:Ethernet Connection
    $portSettings = Get-VMxDefaultSettings $global:hypervResourceSpecs['ntwResTypeEthConn'] $global:hypervResourceSpecs['ntwSubTypeEthConn'] 'Msvm_EthernetPortAllocationSettingData'

    $newPort = $portSettings
  }

  DBGIF $MyInvocation.MyCommand.Name { Is-Null $newPort }
  
  return $newPort
}


function global:Get-VMxOurMacAddressesUsed ([bool] $returnAll)
{
  DBG ('{0}: Parameters: {1}' -f $MyInvocation.MyCommand.Name, (($PSBoundParameters.Keys | ? { $_ -ne 'object' } | % { '{0}={1}' -f $_, $PSBoundParameters[$_]}) -join $multivalueSeparator))

  [System.Collections.ArrayList] $allMACs = @()
  [System.Collections.ArrayList] $ourMACs = @()
  
  #(Get-WmiQueryArray '.' 'SELECT * FROM CIM_EthernetPort WHERE PermanentAddress LIKE "02888800%"' -Namespace $global:virtualizationNamespace) | Select-Object -Expand PermanentAddress)
  #(Get-WmiQueryArray '.' 'SELECT * FROM Msvm_SyntheticEthernetPortSettingData WHERE Address LIKE "02888800%"' -Namespace $global:virtualizationNamespace) | Select-Object -Expand Address)
  (Get-WmiQueryArray '.' 'SELECT * FROM CIM_EthernetPort WHERE PermanentAddress <> NULL AND PermanentAddress <> ""' -Namespace $global:virtualizationNamespace) | % { 
  
    # once, there was a weird MAC address as something like ASCII character <32
    DBGIF ('Weird MAC address in CIM_EthernetPort: {0} | {1}' -f $_.PermanentAddress, ($_ | fl * | Out-String)) { $_.PermanentAddress -notmatch $global:rxMacAddress }
    Add-ListUnique ([ref] $allMACs) $_.PermanentAddress
  }

  (Get-WmiQueryArray '.' 'SELECT * FROM Msvm_SyntheticEthernetPortSettingData WHERE Address <> NULL AND Address <> ""' -Namespace $global:virtualizationNamespace) | Select-Object -Expand Address | % {
  
    DBGIF ('Weird MAC address in  Msvm_SyntheticEthernetPortSettingData: {0}' -f $_) { $_ -notmatch $global:rxMacAddress }
    Add-ListUnique ([ref] $allMACs) $_
  }

  (Get-WmiQueryArray '.' 'SELECT * FROM Win32_NetworkAdapter WHERE MacAddress <> NULL AND MacAddress <> "" AND MacAddress <> "00:00:00:00:00:00"') | Select-Object -Expand MacAddress | % {
  
    DBGIF ('Weird MAC address on Win32_NetworkAdapter: {0}' -f $_) { $_ -notmatch $global:rxMacAddressDotted }
    Add-ListUnique ([ref] $allMACs) $_.Replace(':', '')
  }

  $allMACs.Sort()

  $allMACs | ? { $_ -like ('{0}*' -f $global:sevecekMacPrefix.Replace(':', '')) } | % { [void] $ourMACs.Add($_) }

  DBG ('All MAC addresses found: #{0} | {1}' -f $allMACs.Count, ($allMACs -join ','))
  DBG ('Our MAC addresses found: #{0} | {1}' -f $ourMACs.Count, ($ourMACs -join ','))

  if ($returnAll) {

    return ,$allMACs
  
  } else {

    return ,$ourMACs
  }
}


function global:Get-VMxOurNextFreeMACID ([System.Collections.ArrayList] $usedMacIDs)
{
  DBG ('{0}: Parameters: {1}' -f $MyInvocation.MyCommand.Name, (($PSBoundParameters.Keys | ? { $_ -ne 'object' } | % { '{0}={1}' -f $_, $PSBoundParameters[$_]}) -join $multivalueSeparator))


  if (Is-Null $usedMacIDs) {

    $usedMacIDs = New-Object System.Collections.ArrayList
  }

  [System.Collections.ArrayList] $ourMACs = Get-VMxOurMacAddressesUsed
  #$ourMACs += (Get-WmiObject -Namespace $global:virtualizationNamespace -Query 'SELECT * FROM CIM_EthernetPort WHERE PermanentAddress LIKE "02888800%"' | Select-Object -Expand PermanentAddress)
  #$ourMACs += (Get-WmiObject -Namespace $global:virtualizationNamespace -Query 'SELECT * FROM Msvm_SyntheticEthernetPortSettingData WHERE Address LIKE "02888800%"' | Select-Object -Expand Address)
  #DBG ('Found MACs: {0}' -f (Get-CountSafe $ourMACs))
  
  if ((Get-CountSafe $ourMACs) -gt 0) {

    foreach ($oneMAC in $ourMACs) {
 
      [int] $oneID = 0

      DBG ('One MAC address to be reparsed: {0}' -f $oneMAC)
      
      DBGSTART
      $oneID = [int]::Parse($oneMAC.SubString(8,2), 'HexNumber')
      DBGER $MyInvocation.MyCommand.Name $error
      DBGEND

      Write-Host ("Found MAC: {0}, ID = {1} = 0x{2}" -f $oneMAC, $oneID, $oneID)
      
      if (-not $usedMacIDs.Contains($oneID)) { 

        [void] $usedMacIDs.Add($oneID)
      }
#    
#      if ($oneID -gt $maxID) {
#    
#        $maxID = $oneID
#      }
    }
  }

  $usedMacIDs.Sort()

  DBG ('Existing MAC IDs found: {0}' -f ($usedMacIDs -join ','))
  DBG ('Get the next free MAC ID available')

  #$maxID = [int] ($global:usedMacIDs | Measure-Object -Maximum).Maximum
  #$maxID ++

  [int] $possibleId = 1
  while ($possibleId -lt 256) {

    [int] $idx = 0
    while (($idx -lt $usedMacIDs.Count) -and ($usedMacIDs[$idx] -ne $possibleId)) {

      $idx ++
    }

    if ($idx -ge $usedMacIDs.Count) {

      break
    }

    $possibleId ++
  }

  DBGIF $MyInvocation.MyCommand.Name { $possibleId -ge 256 }
  if ($possibleId -lt 256) {

    DBG ("Returning first free MAC ID: {0}" -f $possibleId)

    [void] $usedMacIDs.Add($possibleId)
    $usedMacIDs.Sort()
    DBG ('Existing MAC IDs which go to the next round: {0}' -f ($usedMacIDs -join ','))
  }
  
  return $possibleId
}


function global:Set-VMxNICNameAndConfig ([WMI] $switchPort, [string] $newName, [string] $assertMac, [string] $internalIP)
{
  DBG ('{0}: Parameters: {1}' -f $MyInvocation.MyCommand.Name, (($PSBoundParameters.Keys | ? { $_ -ne 'object' } | % { '{0}={1}' -f $_, $PSBoundParameters[$_]}) -join $multivalueSeparator))

  DBGIF $MyInvocation.MyCommand.Name { Is-Null $switchPort }
  DBGIF $MyInvocation.MyCommand.Name { Is-EmptyString $newName }
  DBGIF $MyInvocation.MyCommand.Name { $assertMac -like '*:*' }
  DBGIF $MyInvocation.MyCommand.Name { (Is-ValidString $internalIP) -and ((Count-MultiValue $internalIP) -ne 2) }

  if ((Is-NonNull $switchPort) -and (Is-ValidString $newName)) {

    DBG ('Get LAN endpoint for the internal NIC to determine its MAC address')

    DBGIF $MyInvocation.MyCommand.Name { $switchPort.__CLASS -ne $global:hypervResourceSpecs['ntwSwitchPort'] }
<#
    if ($global:virtualizationWmiAPIversion -ne 1) {

      DBGIF $MyInvocation.MyCommand.Name { $switchPort.__CLASS -eq $global:hypervResourceSpecs['ntwSwitch'] }
      #DBGIF $MyInvocation.MyCommand.Name { $switchPortOrSwitch.ResourceType -ne $global:hypervResourceSpecs['ntwResTypeEthConn'] }
      #DBGIF $MyInvocation.MyCommand.Name { $switchPortOrSwitch.ResourceSubType -ne $global:hypervResourceSpecs['ntwSubTypeEthConn'] }
      #$switchPort = Get-WmiRelated $switchPortOrSwitch $global:hypervResourceSpecs['ntwSwitchPort']

    } else {

      #DBGIF $MyInvocation.MyCommand.Name { $global:virtualizationWmiAPIversion -ne 1 }
      DBGIF $MyInvocation.MyCommand.Name { $switchPortOrSwitch.__CLASS -ne $global:hypervResourceSpecs['ntwSwitchPort'] }

      $switchPort = $switchPortOrSwitch
    }
#>
    $lanEndpoint = Get-WmiRelatedSingle $switchPort $global:hypervResourceSpecs['ntwLanEndpoint']

    DBGIF $MyInvocation.MyCommand.Name { ($global:virtualizationWmiAPIversion -ne 1) -and (Is-ValidString $lanEndpoint.MacAddress) }
    DBGIF $MyInvocation.MyCommand.Name { ($global:virtualizationWmiAPIversion -ne 1) -and ($lanEndpoint.MaxDataSize -ne 0) }
    if ($global:virtualizationWmiAPIversion -ne 1) {

      # Note: weirdly enough, but yes, the directly attached LANEndpoint has not MAC address and
      #       also does not have MaxDataSize, while it is Connected
      #       There is another connected internal LANEndpoint with the same ElementName
      #       which finally has the MAC address and MaxDataSize
      $childLanEndpoint = Get-WmiRelatedSingle $lanEndpoint $global:hypervResourceSpecs['ntwLanEndpoint']
      #DBGIF ('Weird LanEndpoint element names: {0} | {1}' -f $childLanEndpoint.ElementName, $lanEndpoint.ElementName) { $childLanEndpoint.ElementName -ne $lanEndpoint.ElementName }
      $lanEndpoint = $childLanEndpoint
    }

    DBGIF $MyInvocation.MyCommand.Name { Is-Null $lanEndpoint }
    DBGIF $MyInvocation.MyCommand.Name { $lanEndpoint.MaxDataSize -lt 1 }
    DBGIF $MyInvocation.MyCommand.Name { Is-EmptyString $lanEndpoint.MacAddress }
    DBG ('Switch port created with LAN endpoint: {0}' -f $lanEndpoint.MacAddress)
    DBGIF $MyInvocation.MyCommand.Name { $lanEndpoint.MacAddress -like '*:*' }
    DBGIF $MyInvocation.MyCommand.Name { (Is-ValidString $lanEndpoint.MacAddress) -and ($lanEndpoint.MacAddress.Length -ne 12) }
    DBGIF $MyInvocation.MyCommand.Name { (Is-ValidString $assertMac) -and ($assertMac -ne $lanEndpoint.MacAddress) }

    DBGSTART
    $mac = $lanEndpoint.MacAddress
    $macAddressDotted = Get-MacAddressDotted $mac
    DBGER $MyInvocation.MyCommand.Name $error
    DBGEND

    DBG ('Going to rename the internal NIC: {0} | {1} | {2}' -f $lanEndpoint.MacAddress, $macAddressDotted, $newName)
    # Note: since Windows 2019: ServiceName = VMSNPXYMP
    $nicsConnected = Get-WMIQuerySingleObject '.' ('SELECT * FROM Win32_NetworkAdapter WHERE AdapterType = "Ethernet 802.3" AND NetConnectionID <> NULL AND MacAddress = "{0}" AND NetConnectionStatus = 2 AND (ServiceName = "VMSMP" OR ServiceName = "VMSNPXYMP")' -f $macAddressDotted)
    DBGIF $MyInvocation.MyCommand.Name { Is-Null $nicsConnected }

    if (Is-NonNull $nicsConnected) {

      Rename-NIC $nicsConnected $newName
              
      if (Is-ValidString $internalIP) {

        $internalIPAddress = (Split-MultiValue $internalIP)[0]
        $internalIPMask = (Split-MultiValue $internalIP)[1]

        DBG ('Internal NIC IP specified: {0} | {1} | {2}' -f $internalIP, $internalIPAddress, $internalIPMask)
        Configure-NIC $nicsConnected $internalIPAddress $internalIPMask $null $null $false $null 'none' $true
      }
    }
  }
}


function global:Create-VMxSwitch ([string] $switchName, [string] $switchType = 'private', [string] $internalIP, [bool] $noHostConnection)
# $switchType = private, externalOnly, external, internal
{
  DBG ('{0}: Parameters: {1}' -f $MyInvocation.MyCommand.Name, (($PSBoundParameters.Keys | ? { $_ -ne 'object' } | % { '{0}={1}' -f $_, $PSBoundParameters[$_]}) -join $multivalueSeparator))

  DBGIF $MyInvocation.MyCommand.Name { Is-EmptyString $switchName }
  DBGIF $MyInvocation.MyCommand.Name { -not (Contains-Safe @('private', 'externalOnly', 'external', 'internal') $switchType) }
  DBGIF $MyInvocation.MyCommand.Name { (Is-ValidString $internalIP) -and ((Count-MultiValue $internalIP) -ne 2) }

  [WMI] $newSwitch = $null

  if (Is-ValidString $switchName) {


    [string] $macAddress = $null
    [WMI] $bestExternalPort = $null
    [string] $physAdapterName = $null

    if ($switchType -eq 'external') {

      $bestExternalPort = Select-VMxBestExternalPort -mustBeIsolated $noHostConnection
      DBG ('External switch will use physical NIC: {0} | {1} | {2}' -f $bestExternalPort.ElementName, $bestExternalPort.PermanentAddress, $bestExternalPort.DeviceID)
      #DBG ('External switch will use physical NIC: {0} | {1} | {2}' -f $bestExternalPort.NetConnectionID, $bestExternalPort.MacAddress, ($bestExternalPort.IPAddress -join ','))

      DBG ('Find the Win32_NetworkAdapter for the physical NIC in question')
      $foundWin32NetworkAdapter = Get-WmiQuerySingleObject '.' ('SELECT * FROM Win32_NetworkAdapter WHERE Name = "{0}" AND MACAddress = "{1}"' -f $bestExternalPort.ElementName, (Get-MacAddressDotted $bestExternalPort.PermanentAddress))
      DBGIF $MyInvocation.MyCommand.Name { Is-Null $foundWin32NetworkAdapter }
      DBGIF $MyInvocation.MyCommand.Name { Is-EmptyString $foundWin32NetworkAdapter.NetConnectionID }
      DBGIF $MyInvocation.MyCommand.Name { $foundWin32NetworkAdapter.MACAddress.Replace(':', '') -ne $bestExternalPort.PermanentAddress }
      DBG ('External switch will use physical NIC: {0} | {1} | {2} | {3}' -f $bestExternalPort.ElementName, $bestExternalPort.PermanentAddress, $bestExternalPort.DeviceID, $foundWin32NetworkAdapter.NetConnectionID)
      $physAdapterName = $foundWin32NetworkAdapter.NetConnectionID
    }

    if ($switchType -eq 'internal') {

      # Note: SHA256CryptoServiceProvider is available since NETFX v3.5SP1 only
      #$sha = New-Object System.Security.Cryptography.SHA256CryptoServiceProvider
      DBGSTART
      $sha = New-Object System.Security.Cryptography.SHA1CryptoServiceProvider
      $hashBytes = $sha.ComputeHash([System.Text.ASCIIEncoding]::ASCII.GetBytes($switchName))
      $macAddress = '{0}{1:X2}{2:X2}{3:X2}' -f $global:sevecekMacPrefix.Replace(':', ''), $hashBytes[0], $hashBytes[1], $hashBytes[2]
      #$macAddressDotted = '{0}:{1:X2}:{2:X2}:{3:X2}' -f $global:sevecekMacPrefix, $hashBytes[0], $hashBytes[1], $hashBytes[2]
      DBGER $MyInvocation.MyCommand.Name $error
      DBGEND
      DBG ('MAC address generated for internal switch: {0}' -f $macAddress)
    }


    if ($switchType -eq 'external') {

      #[string] $mcTmp = $bestExternalPort.PermanentAddress
      #$macAddress = '{0:X2}:{1:X2}:{2:X2}:{3:X2}:{4:X2}:{5:X2}' -f $mcTmp.SubString(0,2), $mcTmp.SubString(2,2), $mcTmp.SubString(4,2), $mcTmp.SubString(6,2), $mcTmp.SubString(8,2), $mcTmp.SubString(10,2)
      $macAddress = $bestExternalPort.PermanentAddress
      DBG ('The external switch will shadow MAC address from its physical NIC adapter: {0} | {1}' -f $bestExternalPort.PermanentAddress, $macAddress)
    }


    $nicSvc = Get-VMxSMS

    if ($global:virtualizationWmiAPIversion -eq 1) {

      DBG ('Create switch with APIv1: {0}' -f $switchName)
      DBGSTART
      $res = $null
      $res = $nicSvc.CreateSwitch($switchName, $switchName, 1024, '')
      DBGER $MyInvocation.MyCommand.Name $error
      DBGEND
      $jobRes = Wait-HyperVJob ([ref] $res)

      DBGSTART
      $newSwitch = $res.CreatedVirtualSwitch
      DBGER $MyInvocation.MyCommand.Name $error
      DBGEND

      DBGIF $MyInvocation.MyCommand.Name { $switchType -eq 'externalOnly' }

      if (Is-NonNull $newSwitch) {

        DBG ('Switch created, proceed with ports for extenal/internal: {0}' -f $switchType)

        if ($switchType -eq 'external') {
        
          DBG ('Preparing external switch.')
          $externalNIC = $bestExternalPort

          DBG ('Create external switch port for the newly created switch')
          $externalSwitchPort = Add-VMxSwitchPort $newSwitch
          
          DBG ('Creating internal switch port for the newly created switch: {0}' -f (-not $noHostConnection))
          $internalSwitchPort = $null

          if (-not $noHostConnection) {

            $internalSwitchPort = Add-VMxSwitchPort $newSwitch
          }

          if ((Is-NonNull $externalNIC) -and (Is-NonNull $externalSwitchPort)) { # -and (Is-NonNull $internalSwitchPort)) {

            DBG ('Setting up the switch with both the Internal and External switch ports')        
            DBGSTART
            $res = $nicSvc.SetupSwitch($externalSwitchPort, $internalSwitchPort, $externalNIC, ('{0}-Switch-{1}' -f $switchName, (Get-GuidString)), 'HYPER-V ' + $switchName)
            DBGER $MyInvocation.MyCommand.Name $error
            DBGEND
            $jobRes = Wait-HyperVJob ([ref] $res)


            [string] $newNICName = ('HV {0}' -f $switchName)
            if (Is-ValidString $physAdapterName) { $newNICName = ('{0} (on {1})' -f $newNICName, $physAdapterName) }
            Set-VMxNICNameAndConfig $internalSwitchPort $newNICName $null $internalIP
          }
        }

        if ($switchType -eq 'internal') {

          DBG ('Creating internal switch port for the newly created switch - physical computer')
          
          # Note: we must create "switch" port on the switch and the 
          #       virtual NIC in the parent partition (the "ethernet" port)

          $internalSwitchPort = Add-VMxSwitchPort $newSwitch

          DBG ('Create internal ethernet port. MAC address: {0}' -f $macAddress)
          # Note: this one cannot have the same name as its associated switch
          DBGSTART
          $res = $null
          $res = $nicSvc.CreateInternalEthernetPort(('{0}-HostNIC' -f $switchName), $switchName, $macAddress)
          DBGER $MyInvocation.MyCommand.Name $error
          DBGEND
          $jobRes = Wait-HyperVJob ([ref] $res)

          DBGSTART
          [WMI] $internalEthernetPort = $res.CreatedInternalEthernetPort
          DBGER $MyInvocation.MyCommand.Name $error
          DBGEND
          DBGIF $MyInvocation.MyCommand.Name { Is-Null $internalEthernetPort }
          DBG ('Internal ethernet port created: {0} | {1} | {2} | {3}' -f $internalEthernetPort.Name, $internalEthernetPort.ElementName, $internalEthernetPort.PermanentAddress, $internalEthernetPort.__PATH)

          #DBG ('The port has been assigned MAC address: {0}' -f $internalEthernetPort.NetworkAddresses[0])
          #DBGIF $MyInvocation.MyCommand.Name { $macAddress -ne $internalEthernetPort.NetworkAddresses[0] }

          DBG ('Get LanEndpoint for the new internal ethernet port')
          $lanEndpoint = Get-WMIRelatedSingle $internalEthernetPort $global:hypervResourceSpecs['ntwLanEndpoint']
          DBGIF $MyInvocation.MyCommand.Name { Is-Null $lanEndpoint }
          DBGIF $MyInvocation.MyCommand.Name { $lanEndpoint.MacAddress -ne $macAddress }

          if (Is-NonNull $lanEndpoint) {

            DBG ('Connect endpoint with the switchport')
            DBGSTART
            $res = $null
            $res = $nicSvc.ConnectSwitchPort($internalSwitchPort, $lanEndpoint)
            DBGER $MyInvocation.MyCommand.Name $error
            DBGEND
            $jobRes = Wait-HyperVJob ([ref] $res)


            [string] $newNICName = ('HV {0}' -f $switchName)
            if (Is-ValidString $physAdapterName) { $newNICName = ('{0} (on {1})' -f $newNICName, $physAdapterName) }
            Set-VMxNICNameAndConfig $internalSwitchPort $newNICName $macAddress $internalIP
          }
        }
      }

    } else {

      DBG ('Create switch with APIv2: {0}' -f $switchName)
      $switchSettings = Spawn-WMIInstance '.' Msvm_VirtualEthernetSwitchSettingData $global:virtualizationNamespace

      [string[]] $switchPorts = @()

      if (($switchType -eq 'internal') -or (($switchType -eq 'external') -and (-not $noHostConnection))) {

        DBG ('Create INTERNAL switch port both for INTERNAL and EXTERNAL switch')

        $internalSwitchPort = Add-VMxSwitchPort

        DBGWMI $internalSwitchPort

        $nicPortName = '{0}-InternalPort-{1}' -f $switchName, (Get-GuidString)
        $internalNicPortName = $nicPortName
        $nicPortHostResource = (Get-HyperVHost).__PATH
        DBG ('Set the internal port parameters: {0} | {1} | {2}' -f $nicPortName, $macAddress, $nicPortHostResource)

        #if ($switchType -eq 'internal') {

          DBG ('Set MAC address for the port: {0}' -f $macAddress)
          DBGSTART
          $internalSwitchPort.Address = $macAddress
          DBGER $MyInvocation.MyCommand.Name $error
          DBGEND
        #}

        DBGSTART
        $internalSwitchPort.ElementName = $nicPortName
        $internalSwitchPort.HostResource = $nicPortHostResource
        #$newPort = $res.ResultingSystem
        DBGER $MyInvocation.MyCommand.Name $error
        DBGEND

        DBG ('Get the INTERNAL switch port description text')
        DBGSTART
        $switchPorts = @($internalSwitchPort.GetText(1))
        DBGER $MyInvocation.MyCommand.Name $error
        DBGEND
      }

      if ($switchType -eq 'external') {

        DBG ('Create EXTERNAL switch port')

        $externalSwitchPort = Add-VMxSwitchPort

        DBGWMI $externalSwitchPort

        $nicPortName = '{0}-ExternalPort-{1}' -f $switchName, (Get-GuidString)
        $nicPortHostResource = $bestExternalPort.__PATH
        DBG ('Set the external port parameters: {0} | {1} | {2}' -f $nicPortName, $macAddress, $nicPortHostResource)

        DBGSTART
        $externalSwitchPort.ElementName = $nicPortName
        $externalSwitchPort.HostResource = $nicPortHostResource
        DBGER $MyInvocation.MyCommand.Name $error
        DBGEND

        DBG ('Get the EXTERNAL switch port description text')
        DBGSTART
        $switchPorts += $externalSwitchPort.GetText(1)
        DBGER $MyInvocation.MyCommand.Name $error
        DBGEND
      }

      DBG ('Create the switch finally with ports: {0}' -f $switchPorts.Count)

      DBGSTART
      $switchSettings.ElementName = $switchName
      $res = $nicSvc.DefineSystem($switchSettings.GetText(1), $switchPorts, $null)
      DBGER $MyInvocation.MyCommand.Name $error
      DBGEND
      $jobRes = Wait-HyperVJob ([ref] $res) ResultingSystem Msvm_VirtualEthernetSwitch

      DBGSTART
      $newSwitch = $res.ResultingSystem
      DBGER $MyInvocation.MyCommand.Name $error
      DBGEND

      DBGWMI $newSwitch


      if (($switchType -eq 'internal') -or (($switchType -eq 'external') -and (-not $noHostConnection))) {

        DBG ('Get the associated switch port back from the switch created: {0}' -f $nicPortName)
        # Note: in case of purely internal switch, the $nicPortName contains the name of the internal
        #       port. In case of exteranl switch, the value of $nicPortName is at first the name of 
        #       the internal port and then changes to the name of the external port
        $switchPortToRename = (Get-WmiRelated $newSwitch $global:hypervResourceSpecs['ntwSwitchPort']) | ? { $_.ElementName -eq $internalNicPortName }
        DBGIF $MyInvocation.MyCommand.Name { Is-Null $switchPortToRename }
        DBGIF $MyInvocation.MyCommand.Name { (Get-CountSafe $switchPortToRename) -ne 1 }

        DBGIF $MyInvocation.MyCommand.Name { ($switchPortToRename.ElementName -notlike '*-InternalPort-*') }
        #DBGIF $MyInvocation.MyCommand.Name { ($switchType -eq 'external') -and (Is-ValidString $macAddress) }

        [string] $newNICName = ('HV {0}' -f $switchName)
        if (Is-ValidString $physAdapterName) { $newNICName = ('{0} (on {1})' -f $newNICName, $physAdapterName) }
        Set-VMxNICNameAndConfig $switchPortToRename $newNICName $macAddress $internalIP
      }
    }
  }

  DBGIF $MyInvocation.MyCommand.Name { Is-Null $newSwitch }

  return $newSwitch
}


function global:Assert-VMxHyperVSwitch ([string] $nameAndFlags, [string] $internalIP)
{
  DBG ('{0}: Parameters: {1}' -f $MyInvocation.MyCommand.Name, (($PSBoundParameters.Keys | ? { $_ -ne 'object' } | % { '{0}={1}' -f $_, $PSBoundParameters[$_]}) -join $multivalueSeparator))

  $switchName = Strip-ValueFlags $nameAndFlags
  $externalSwitch = Has-ValueFlags $nameAndFlags E
  $noHostConnection = $externalSwitch -and (Has-ValueFlags $nameAndFlags X)
  $internalSwitch = Has-ValueFlags $nameAndFlags I
    
  $switchFound = $null
  
  # find an existing switch or create a new one if required
  DBG ('Switch: {0} | external = {1} | internal = {2}' -f $switchName, $externalSwitch, $internalSwitch)

  if ((Is-ValidString $switchName) -and ($switchName -ne $global:emptyValueMarker)) {

    DBG ('Try to find the switch first if existing: {0}' -f $switchName)
    $switchFound = $null
    $switchFound = Get-WmiQuerySingleObject '.' ('SELECT * FROM {0} WHERE ElementName = "{1}"' -f $hypervResourceSpecs['ntwSwitch'], $switchName) $false $global:virtualizationNamespace
      
    if (Is-Null $switchFound) {

      DBG ('Switch not found, creating')

      if ($externalSwitch) {
        
        $switchFound = Create-VMxSwitch $switchName external -noHostConnection $noHostConnection

      } elseif ($internalSwitch) {

        DBGIF $MyInvocation.MyCommand.Name { Is-EmptyString $internalIP }
        $switchFound = Create-VMxSwitch $switchName internal $internalIP

      } else {

        $switchFound = Create-VMxSwitch $switchName private
      }
    
    } else {

      DBG ('Existing switch found')
    }
  }

  DBGIF $MyInvocation.MyCommand.Name { Is-Null $switchFound }
  DBGIF $MyInvocation.MyCommand.Name { $switchFound.Description -ne 'Microsoft Virtual Switch' }
  DBGIF $MyInvocation.MyCommand.Name { $switchFound.Caption -ne 'Virtual Switch' }

  DBG ('Returning switch found: {0} | {1} | {2}' -f $switchFound.ElementName, $switchFound.Name, $switchFound.__PATH)

  return $switchFound
}


function global:Select-VMxBestExternalPort ([bool] $mustBeIsolated)
{
  DBG ('{0}: Parameters: {1}' -f $MyInvocation.MyCommand.Name, (($PSBoundParameters.Keys | ? { $_ -ne 'object' } | % { '{0}={1}' -f $_, $PSBoundParameters[$_]}) -join $multivalueSeparator))

  # Note: EnabledState = 2 ... enabled
  #       EnabledState = 6 ... enabled but offline
  #       EnabledState = 3 ... disabled

  # Note: Windows VPN adapter connected/disconnected
  <#

not present in the list of Msvm_ExternalEthernetPort at all

  #>

  # Note: ethernet NIC plugged-in with Hyper-V switch bound
  <#

ActiveMaximumTransmissionUnit    : 1500
EnabledDefault                   : 2
EnabledState                     : 2
HealthState                      : 5
IsBound                          : True
LinkTechnology                   : 2
MaxDataSize                      : 1500
MaxSpeed                         : 1000000000
PermanentAddress                 : 6805CA469267
Speed                            : 1000000000
Status                           : OK
OperationalStatus                : {2}
PortType                         : 53
SupportedMaximumTransmissionUnit : 1500
UsageRestriction                 : 4

  #>

  # Note: ethernet NIC plugged-in without Hyper-V switch bound
  <#

ActiveMaximumTransmissionUnit    : 1500
EnabledDefault                   : 2
EnabledState                     : 2
HealthState                      : 5
IsBound                          : False
LinkTechnology                   : 2
MaxDataSize                      : 1500
MaxSpeed                         : 1000000000
PermanentAddress                 : 6805CA469267
Speed                            : 1000000000
Status                           : OK
OperationalStatus                : {2}
PortType                         : 53
SupportedMaximumTransmissionUnit : 1500
UsageRestriction                 : 4

  #>

  # Note: ethernet NIC unplugged while enabled
  # Note: ethernet NIC plugged-in while disabled
  <#

ActiveMaximumTransmissionUnit    : 1500
EnabledDefault                   : 2
EnabledState                     : 2
HealthState                      : 5
IsBound                          : False
LinkTechnology                   : 2
MaxDataSize                      : 1500
MaxSpeed                         : 0
PermanentAddress                 : 6805CA469267
Speed                            : 0
Status                           : OK
OperationalStatus                : {2}
PortType                         : 53
SupportedMaximumTransmissionUnit : 1500
UsageRestriction                 : 4

  #>

  # Note: cisco virtual VPN adapter disconnected (appears as disabled in GUI)
  # Note: ethernet NIC removed from computer while drivers installed
  <#

ActiveMaximumTransmissionUnit    : 0
EnabledDefault                   : 2
EnabledState                     : 6
HealthState                      : 5
IsBound                          : False
LinkTechnology                   : 2
MaxDataSize                      : 0
MaxSpeed                         : 0
PermanentAddress                 : 00059A3C7A00
Speed                            : 0
Status                           : OK
OperationalStatus                : {12}
PortType                         : 53
SupportedMaximumTransmissionUnit : 0
UsageRestriction                 : 4

  #>

  # Note: cisco virtual VPN adapter connected
  <#

ActiveMaximumTransmissionUnit    : 1331
EnabledDefault                   : 2
EnabledState                     : 2
HealthState                      : 5
IsBound                          : False
LinkTechnology                   : 2
MaxDataSize                      : 1331
MaxSpeed                         : 995000000
PermanentAddress                 : 00059A3C7A00
Speed                            : 995000000
Status                           : OK
OperationalStatus                : {12}
PortType                         : 53
SupportedMaximumTransmissionUnit : 1331
UsageRestriction                 : 4

  #>
 

  if ($mustBeIsolated) {

    $prefItems = @(
      'i01_BindTcp_LinkUp_APIPA_NoDG',
      'i02_BindTcp_LinkUp_Dhcp_NoDG',
      'i03_BindTcp_LinkUp_AnyIP_NoDG',
      'i04_BindTcp_LinkUp_NoIP',
      'i05_BindTcp_LinkDown_BindTcp'
      )
  
  } else {

    $prefItems = @(
      'i01_BindTcp_LinkUp_Dhcp_DG',
      'i02_BindTcp_LinkUp_Dhcp_NoDG',
      'i03_BindTcp_LinkUp_Dhcp',
      'i04_BindTcp_LinkUp_AnyIP',
      'i05_BindTcp_LinkUp',
      'i06_BindTcp_AnyIP',
      'i07_BindTcp'
      )
  }

  [object[]] $physNICs = Get-BestServerInternalNIC -subCondition { (($_.ServiceName -ne 'VMSMP') -and ($_.ServiceName -ne 'VMSNPXYMP')) -and ($_.NameIndexed -notlike 'Cisco AnyConnect*') } -preferenceItemsRequested $prefItems -returnAll $true -doDefault $false -doNonMatching $false -doNotAssert $true
  DBG ('Possible NICs in order: #{0} | {1}' -f (Get-CountSafe $physNICs), ($physNICs | select MacAddress, @{ n = 'Stat' ; e = { $_.NetConnectionStatus } }, NameIndexed, PrefItemMatched, @{ n = 'IP' ; e = { $_.IPAddress | Select -f 1 } }, @{ n = 'Idx' ; e = { $_.InterfaceIndex } } | ft -auto | out-string))
  DBGIF $MyInvocation.MyCommand.Name { (Get-CountSafe $physNICs) -lt 1 }

  $extPorts = Get-WmiQueryArray '.' 'SELECT * FROM Msvm_ExternalEthernetPort WHERE (EnabledState <> 6) AND (NOT (Name LIKE "Cisco AnyConnect%"))' $global:virtualizationNamespace
  DBG ("External ports found: #{0} | -->`r`n{1}" -f (Get-CountSafe $extPorts), ($extPorts | select ElementName, PermanentAddress, PortType, EnabledState, IsBound | Out-String))
  $extPorts | % {
  
    DBG ('One external port: {0} | mac = {1} | id = {2} | port = {3} | default = {4} | enabled = {5} | health = {6} | bound = {7}' -f $_.ElementName, $_.PermanentAddress, $_.DeviceID, $_.PortType, $_.EnabledDefault, $_.EnabledState, $_.HealthState, $_.IsBound)
    DBGIF $MyInvocation.MyCommand.Name { Is-EmptyString $_.PermanentAddress }
    # Note: the MAC address might be 000000000000 if the driver for the physical NIC is not loaded/installed (Windows cannot load the device driver for this hardware)
    DBGIF $MyInvocation.MyCommand.Name { $_.PermanentAddress -eq '000000000000' }
    DBGIF $MyInvocation.MyCommand.Name { $_.PermanentAddress -match '[^a-f0-9]' }
  }

  #$physNICs = Get-WMIObject -Query 'SELECT * FROM Win32_NetworkAdapterConfiguration WHERE MACAddress <> NULL AND ServiceName <> "VMSMP"' | Select-Object @{ Name = 'MACAddress'; Expression = { $_.MACAddress.Replace(':', '').Trim() }}, Description, IPAddress, IPEnabled, DHCPEnabled, DHCPServer, DefaultIPGateway
  #DBG ('Physical NICs found: {0}' -f ($physNICs | Out-String))

  DBGIF $MyInvocation.MyCommand.Name { Is-NonNull ($physNICs | ? { $_.PnPStatus -ne 'OK' }) }
  #DBGIF $MyInvocation.MyCommand.Name { (Get-CountSafe $physNICs) -ne ($extPorts | ? { -not $_.IsBound } | Measure).Count }

<#
  $virtNICs = Get-WMIObject -Query 'SELECT * FROM Win32_NetworkAdapterConfiguration WHERE MACAddress <> NULL AND ServiceName = "VMSMP" AND MACAddress <> "00:00:00:00:00:00"' | Select-Object @{ Name = 'MACAddress'; Expression = { $_.MACAddress.Replace(':', '').Trim() }}, Description, IPAddress, IPEnabled, DHCPEnabled, DHCPServer, DefaultIPGateway
  DBG ('Virtual NICs found: {0}' -f ($virtNICs | Out-String))

  $onlyPhysNICs = $physNICs | % {

    $onePhysNIC = $_

    $found = $false
    $virtNICs | % {

      $oneVirtNIC = $_

      if ($oneVirtNIC.MACAddress -eq $onePhysNIC.MACAddress) { $found = $true }
    }

    if (-not $found) { $onePhysNIC }
  }

  DBG ('Non-shadowed physical NICs found: {0}' -f ($onlyPhysNICs | Out-String))

  # ipEnabled dhcp+DG
  # ipEnabled dhcp noDG
  # ipEnabled dhcp noIP
  # ipEnabled
  # unplugged nonIPEnabled

  DBG ('Going to determine the best NIC.')
  $bestNIC = $null
  $bestNIC = $onlyPhysNICs | ? { ($_.IPEnabled -eq $true) -and ($_.DHCPEnabled -eq $true) -and ($_.DHCPServer -ne $null) -and ($_.IPAddress.Count -gt 0) -and ($_.DefaultIPGateway.Count -gt 0) } | Select-Object -First 1

  if (($bestNIC.MACAddress -eq $null) -or ($bestNIC.MACAddress -eq '')) {

    DBG ('Second best NIC trial')
    $bestNIC = $null
    $bestNIC = $onlyPhysNICs | ? { ($_.IPEnabled -eq $true) -and ($_.DHCPEnabled -eq $true) -and ($_.DHCPServer -ne $null) -and ($_.IPAddress.Count -gt 0) } | Select-Object -First 1

    if (($bestNIC.MACAddress -eq $null) -or ($bestNIC.MACAddress -eq '')) {

      DBG ('Third best NIC trial')
      $bestNIC = $null
      $bestNIC = $onlyPhysNICs | ? { ($_.IPEnabled -eq $true) -and ($_.DHCPEnabled -eq $true) } | Select-Object -First 1

      if (($bestNIC.MACAddress -eq $null) -or ($bestNIC.MACAddress -eq '')) {

        DBG ('Fourth best NIC trial')
        $bestNIC = $null
        $bestNIC = $onlyPhysNICs | ? { ($_.IPEnabled -eq $true) } | Select-Object -First 1

        if (($bestNIC.MACAddress -eq $null) -or ($bestNIC.MACAddress -eq '')) {

          DBG ('Fifth best NIC trial')
          $bestNIC = $null
          $bestNIC = $onlyPhysNICs | ? { ($_.IPEnabled -ne $true) } | Select-Object -First 1
        }
      }
    }
  }
#>

  $bestNIC = $null
  $bestPorts = $null
  $bestPort = $null
  $bestWMIPort = $null

  if ((Get-CountSafe $physNICs) -gt 0) {

    $bestNIC = $physNICs | Select -f 1
    DBG ('Best NIC found: Idx = {0} | NameId = {1} | IP = {2} | Mac = {3}' -f $bestNIC.InterfaceIndex, $bestNIC.NameIndexed, ($bestNIC.IPAddress | Select -f 1), $bestNIC.MacAddress)

    $bestPorts = $extPorts | sort -Descending Speed | ? { $_.PermanentAddress -eq $bestNIC.MACAddress.Replace(':', '') }
    DBGIF $MyInvocation.MyCommand.Name { (Get-CountSafe $bestPorts) -ne 1 }

    $bestPort = $bestPorts | ? { -not $_.IsBound } | Select -f 1
    DBGIF $MyInvocation.MyCommand.Name { Is-Null $bestPort }

    if (Is-Null $bestPort) {

      DBG ('No extPort maches bestNIC MAC address, going the rogue way')
      $bestPort = $bestPorts | Select -f 1
    }

    DBGIF $MyInvocation.MyCommand.Name { $bestPort.ElementName -ne $bestNIC.NameIndexed }
  
  } else {

    DBG ('No best NIC available, going the rogue way: {0}' -f (-not $mustBeIsolated))
  }

  if ((-not $mustBeIsolated) -and ((Get-CountSafe $extPorts) -gt 0)) {

    if (Is-Null $bestPort) {
      
      DBG ('No valid NIC, going the rogue way')
      $bestPort = $extPorts | sort -Descending Speed | ? { -not $_.IsBound } | Select -f 1
    }

    if (Is-Null $bestPort) {
      
      DBG ('Really invalid NICs, going the rogue way')
      $bestPort = $extPorts | sort -Descending Speed | Select -f 1
    }
  }


  if (Is-NonNull $bestPort) {

    DBG ('Best port found: {0} | {1} | {2}' -f $bestPort.ElementName, $bestPort.PermanentAddress, $bestPort.DeviceID)

    $bestWMIPort = Get-WmiQuerySingleObject '.' ('SELECT * FROM Msvm_ExternalEthernetPort WHERE DeviceID = "{0}"' -f $bestPort.DeviceID) -namespace $global:virtualizationNamespace
    DBGIF $MyInvocation.MyCommand.Name { Is-Null $bestWMIPort }  

    DBGIF $MyInvocation.MyCommand.Name { $bestWMIPort.ISBound -eq $true }
    if ($bestWMIPort.ISBound -eq $true) {
  
      $bestWMIPort = $null
    }
  }  

  DBG ('Best Msvm_ExternalEthernetPort port found: {0} | {1} | {2}' -f $bestWMIPort.ElementName, $bestWMIPort.PermanentAddress, $bestWMIPort.DeviceID)
  DBGIF $MyInvocation.MyCommand.Name { Is-Null $bestWMIPort }

  return $bestWMIPort
}


function global:Set-VMxNICs ($vm, [string] $nics, [string] $internalIP, [System.Collections.ArrayList] $usedMacIDs)
{
  DBG ('{0}: Parameters: {1}' -f $MyInvocation.MyCommand.Name, (($PSBoundParameters.Keys | ? { $_ -ne 'object' } | % { '{0}={1}' -f $_, $PSBoundParameters[$_]}) -join $multivalueSeparator))
  DBGIF $MyInvocation.MyCommand.Name { Is-Null $vm }

  if ((Is-NonNull $vm) -and (Is-ValidString $nics) -and ($nics -ne $global:emptyValueMarker)) {

    DBG ('VM to configure the NICs for: {0} | {1}' -f $vm.ElementName, $vm.Name)

    $nicList = $nics.Split("|")
    DBG ('NIC list contains NICs: {0}' -f (Get-CountSafe $nicList))
  
    if ((Get-CountSafe $nicList) -gt 0) {

      $vmMACID = Get-VMxOurNextFreeMACID $usedMacIDs
      [int] $nicMACID = 1

      foreach ($nic in $nicList)
      {
        DBG ('Attaching to switch: {0} | nicID = {1}' -f $nic, $nicMACID)
        $switchName = Strip-ValueFlags $nic
        $externalSwitch = Has-ValueFlags $nic E
        $internalSwitch = Has-ValueFlags $nic I
        $legacyAdapter = Has-ValueFlags $nic L
        $macSpoofing = Has-ValueFlags $nic S
        $nicTeaming = Has-ValueFlags $nic T

        DBGIF $MyInvocation.MyCommand.Name { ($macSpoofing -and $nicTeaming) -and ($global:virtualizationWmiAPIversion -eq 1) }
    
        $switchFound = Assert-VMxHyperVSwitch $nic $internalIP
        if (Is-NonNull $switchFound) {

          $adapterName = 'Network Adapter-{1}-{0}-{2}' -f $switchName, $nicMACID, $vm.ElementName
        
          if (-not $externalSwitch) {

            DBG ('Configuring static MAC address for the switch port.')
            $macAddressStatic = $true
            $macAddress = '{0}00{1:X2}{2:X2}' -f $global:sevecekMacPrefix.Replace(':', ''), $vmMACID, $nicMACID
            DBG ('Statis MAC address constructed: {0}' -f $macAddress)

          } else {

            $macAddressStatic = $false
            $macAddress = ''
            DBG ('Will use dynamic MAC address')
          }

          # Note: we must increment the NIC ID on every NIC regardless of its static/dynamic setting
          #       because the ID is then index into the multivalue of names inside guest
          $nicMACID ++


          if ($global:virtualizationWmiAPIversion -eq 1) {

            DBG ('Creating VM ethernet adapter with v1: {0} | {1} | legacy = {2}' -f $nic, $adapterName, $legacyAdapter)
            if (-not $legacyAdapter) {

              $newEthernetAdapter = New-VMxSettings $global:hypervResourceSpecs['ntwResTypeEthPort'] $global:hypervResourceSpecs['ntwSubTypeEthPort'] $adapterName

            } else {

              $newEthernetAdapter = New-VMxSettings $global:hypervResourceSpecs['ntwResTypeLegacyEthPort'] $global:hypervResourceSpecs['ntwSubTypeLegacyEthPort'] $adapterName
            }

            $nicSvc =  Get-VMxSMS
  
            $switchPortGuid = '{0}-{1}' -f $adapterName, (Get-GuidString)
            DBG ('Creating new switch port for the VM: {0}' -f $switchPortGuid)
            DBGSTART
            $res = $null
            $res = $nicSvc.CreateSwitchPort($switchFound, $switchPortGuid, $switchPortGuid)
            DBGER $MyInvocation.MyCommand.Name $error
            DBGEND
            #DBGWMI $res
            $switchPort = [wmi] $res.CreatedSwitchPort
            DBGIF $MyInvocation.MyCommand.Name { Is-Null $switchPort }

            DBG ('Connecting the external switch port to VM: staticMac = {0} | mac = {1}' -f $macAddressStatic, $macAddress)
            DBGSTART
            $newEthernetAdapter.Connection = $switchPort
            $newEthernetAdapter.StaticMacAddress = $macAddressStatic

            if ($macAddressStatic) {

              $newEthernetAdapter.Address = $macAddress
            }
            DBGER $MyInvocation.MyCommand.Name $error
            DBGEND

            DBG ('New NIC adapter: {0} | {1} | {2} | {3} | {4}' -f $newEthernetAdapter.Caption, $newEthernetAdapter.StaticMacAddress, $newEthernetAdapter.Address, $newEthernetAdapter.ResourceSubType, $newEthernetAdapter.__Path)

            Add-VMxResource $vm $newEthernetAdapter | Out-Null

          } else {

            DBG ('Creating VM ethernet adapter with v2: {0} | {1} | legacy = {2}' -f $nic, $adapterName, $legacyAdapter)
            # 10, Microsoft:Hyper-V:Synthetic Ethernet Port, Msvm_SyntheticEthernetPortSettingData

            if (-not $legacyAdapter) {

              $newEthernetAdapter = Get-VMxDefaultSettings $global:hypervResourceSpecs['ntwResTypeEthPort'] $global:hypervResourceSpecs['ntwSubTypeEthPort'] 
              DBGIF $MyInvocation.MyCommand.Name { $newEthernetAdapter.__CLASS -ne $global:hypervResourceSpecs['ntwWmiTypeEthPort'] }

            } else {

              $newEthernetAdapter = Get-VMxDefaultSettings $global:hypervResourceSpecs['ntwResTypeLegacyEthPort'] $global:hypervResourceSpecs['ntwSubTypeLegacyEthPort'] 
              DBGIF $MyInvocation.MyCommand.Name { $newEthernetAdapter.__CLASS -ne $global:hypervResourceSpecs['ntwWmiTypeLegacyEthPort'] }
            }

            DBGSTART
            if (-not $legacyAdapter) {

              # Note: the .VirtualSystemIdentifiers member is not present on Msvm_EmulatedEthernetPortSettingData class
              $newEthernetAdapter.VirtualSystemIdentifiers = @((Get-GuidString $true))
            }

            $newEthernetAdapter.ElementName = $adapterName

            #$newEthernetAdapter.StaticMacAddress = $false

            $newEthernetAdapter.StaticMacAddress = $macAddressStatic
            if ($macAddressStatic) {

              $newEthernetAdapter.Address = $macAddress
            }
            DBGER $MyInvocation.MyCommand.Name $error
            DBGEND

            $addedEthernetAdapter = Add-VMxResource (Get-VMxSettings $vm) $newEthernetAdapter
            DBGWMI $addedEthernetAdapter


            DBG ('Connect the ethernet port with the switch')
            # 33, Microsoft:Hyper-V:Ethernet Connection
            $portSettings = Get-VMxDefaultSettings $global:hypervResourceSpecs['ntwResTypeEthConn'] $global:hypervResourceSpecs['ntwSubTypeEthConn'] 'Msvm_EthernetPortAllocationSettingData'

            DBGSTART
            $portSettings.Parent = $addedEthernetAdapter.__PATH
            $portSettings.HostResource = @($switchFound.__PATH)
            DBGER $MyInvocation.MyCommand.Name $error
            DBGEND

            # Note: this is the created 'Msvm_EthernetPortAllocationSettingData'
            #       which could be obtained even later by going this way:
            #       $epasd = Get-WMIRelated $vmSettings 'Msvm_EthernetPortAllocationSettingData'
            $newPortSettingsAdded = Add-VMxResource $vm $portSettings

            if ((Is-NonNull $newPortSettingsAdded) -and ($macSpoofing -or $nicTeaming)) {

              DBG ('Enabling MAC spoofing for the ethernet switch port')
              $vmms = Get-VMxMS
              $vmSettings = Get-VMxSettings $vm

              $espssd = Spawn-WMIInstance '.' 'Msvm_EthernetSwitchPortSecuritySettingData' $global:virtualizationNamespace

              DBG ('Set the security configuration options: spoofing = {0} | teaming = {1}' -f $macSpoofing, $nicTeaming)
              DBGSTART
              $espssd.AllowMacSpoofing = $macSpoofing
              $espssd.AllowTeaming = $nicTeaming
              DBGER $MyInvocation.MyCommand.Name $error
              DBGEND

              if (Is-NonNull $espssd) {

                DBG ('Add the new configured feature settings')
                DBGSTART
                $res = $null
                $res = $vmms.AddFeatureSettings($newPortSettingsAdded, $espssd.GetText(1))
                DBGER $MyInvocation.MyCommand.Name $error
                DBGEND
                $jobRes = Wait-HyperVJob ([ref] $res)
                DBGIF $MyInvocation.MyCommand.Name { (Is-Null $res) -or ((Get-CountSafe $res.ResultingFeatureSettings) -lt 1) }
              }
            }
          }          
        }
      }
    }
  
  } else {

    DBG ('No NICs configuration requested')
  }
}



function global:Get-HyperVHost ()
{
  DBG ('{0}: Parameters: {1}' -f $MyInvocation.MyCommand.Name, (($PSBoundParameters.Keys | ? { $_ -ne 'object' } | % { '{0}={1}' -f $_, $PSBoundParameters[$_]}) -join $multivalueSeparator))

  $hypervHost = Get-WmiQuerySingleObject '.' ('SELECT * FROM Msvm_ComputerSystem WHERE Caption = "Hosting Computer System" AND Description = "Microsoft Hosting Computer System" AND ElementName = "{0}"' -f $global:thisComputerHost) -namespace $global:virtualizationNamespace

  DBGIF $MyInvocation.MyCommand.Name { Is-Null $hypervHost }
  DBGIF $MyInvocation.MyCommand.Name { $hypervHost.Status -ne 'OK' }

  return $hypervHost
}


function global:Get-VMxLastStateChange ([WMI] $vm)
{
  DBGIF $MyInvocation.MyCommand.Name { Is-Null $vm }
  DBGIF $MyInvocation.MyCommand.Name { Is-EmptyString $vm.TimeOfLastStateChange }

  $lastStateChange = [DateTime]::MinValue

  if ((Is-NonNull $vm) -and (Is-ValidString $vm.TimeOfLastStateChange)) {

    DBGSTART
    $lastStateChange = [System.Management.ManagementDateTimeConverter]::ToDateTime($vm.TimeOfLastStateChange)
    DBGER $MyInvocation.MyCommand.Name $error
    DBGEND
  }

  #DBGIF ('Weird VM last state change: {0} | {1:s}' -f $vm.ElementName, $lastStateChange) { $lastStateChange -lt ([DateTime]::Parse('2000-01-01')) }
  return $lastStateChange
}

function global:Get-VMxs ([string] $name, [bool] $assertNonexisting = $true, [bool] $assertMultiple = $true, [ref] $dispoList)
{
  DBG ('{0}: Parameters: {1}' -f $MyInvocation.MyCommand.Name, (($PSBoundParameters.Keys | ? { $_ -ne 'object' } | % { '{0}={1}' -f $_, $PSBoundParameters[$_]}) -join $multivalueSeparator))
  DBGIF $MyInvocation.MyCommand.Name { Is-EmptyString $name }

  $existingVMs = Get-WmiQueryArray '.' ('{0} AND ElementName = "{1}"' -f $global:wmiFltVirtualMachines, $name) $global:virtualizationNamespace $false

  DBGIF $MyInvocation.MyCommand.Name { $assertNonexisting -and ((Get-CountSafe $existingVMs) -eq 0) }
  DBGIF $MyInvocation.MyCommand.Name { $assertMultiple -and ((Get-CountSafe $existingVMs) -gt 1) }

  if ((Get-CountSafe $existingVMs) -gt 0) { foreach ($oneExistingVM in $existingVMs) {

    [string] $oneExistingVMStatus = ''
    [string] $oneExistingVMSubstatus = ''

    $oneExistingVMStatus = $global:vmOperationalStatus[([int] $oneExistingVM.OperationalStatus[0])]
    
    if ($oneExistingVM.OperationalStatus.Count -gt 1) {

      $oneExistingVMSubstatus = $global:vmOperationalStatus[([int] $oneExistingVM.OperationalStatus[1])]
    }
    
    $isVmRunning = $oneExistingVM.EnabledState -eq 2
    $isVmStopped = $oneExistingVM.EnabledState -eq 3
    $lastStateChange = Get-VMxLastStateChange $oneExistingVM

    DBG ('Got a VM: {0} | {1} | installed = {2:s} | status = {3} | substatus = {4} | running = {5} | stopped = {6} | lastChange = {7:s}' -f $oneExistingVM.ElementName, $oneExistingVM.Name, ([System.Management.ManagementDateTimeconverter]::ToDateTime($oneExistingVM.InstallDate)), $oneExistingVMStatus, $oneExistingVMSubstatus, $isVmRunning, $isVmStopped, $lastStateChange)
    DBGIF ('Weird VM: {0} | {1} | {2}' -f $oneExistingVM.Name, $oneExistingVM.Description, $oneExistingVM.Caption) { ($oneExistingVM.Description -ne 'Microsoft Virtual Machine') -or ($oneExistingVM.Caption -ne 'Virtual Machine') -or ($oneExistingVM.Name -notmatch $global:rxGUID) }
    DBGIF $MyInvocation.MyCommand.Name { Is-EmptyString $oneExistingVMStatus }
    DBGIF $MyInvocation.MyCommand.Name { ($oneExistingVM.OperationalStatus[0] -ne 2) -and (Is-ValidString $oneExistingVM.OperationalStatus[1]) }
  }}

  if (Is-NonNull $dispoList) {

    [void] $dispoList.Value.AddRange($existingVMs)
  }

  return ,$existingVMs
}


function global:Get-VMxAll ([ref] $dispoList)
{
  DBG ('{0}: Parameters: {1}' -f $MyInvocation.MyCommand.Name, (($PSBoundParameters.Keys | ? { $_ -ne 'object' } | % { '{0}={1}' -f $_, $PSBoundParameters[$_]}) -join $multivalueSeparator))

  $existingVMs = $null
  $existingVMs = Get-WmiQueryArray '.' $global:wmiFltVirtualMachines $global:virtualizationNamespace $false

  DBG ('Found VMs: # = {0} | {1}' -f (Get-CountSafe $existingVMs), (($existingVMs | Select -Expand ElementName) -join ','))

  if (Is-NonNull $dispoList) {

    [void] $dispoList.Value.AddRange($existingVMs)
  }

  return ,$existingVMs
}


function global:Get-VMxSingle ([string] $name, [bool] $assertNonexisting = $true)
{
  $existingVM = $null
  $existingVMs = Get-VMxs $name -assertNonexisting $assertNonexisting -assertMultiple $true
  
  if ((Get-CountSafe $existingVMs) -ge 1) {

    $existingVM = $existingVMs[0]
  }

  return $existingVM
}


function global:Remove-VMx ([object] $customVM)
{
  DBG ('{0}: Parameters: {1}' -f $MyInvocation.MyCommand.Name, (($PSBoundParameters.Keys | ? { $_ -ne 'object' } | % { '{0}={1}' -f $_, $PSBoundParameters[$_]}) -join $multivalueSeparator))
  DBGIF $MyInvocation.MyCommand.Name { Is-Null $customVM }

  DBG ('Should remove VM: {0} | {1}' -f $customVM.name, $customVM.baseFolder)

  DBGIF ('Must not delete PERMANENT-* VM: {0}' -f $customVM.name) { $customVM.name -like $global:hypervPermanent }

  if ((Is-ValidString $customVM.name) -and ($customVM.name -notlike $global:hypervPermanent)) {

    $existingVM = Get-VMxs $customVM.name -assertNonexisting $true -assertMultiple $true

    if ((Get-CountSafe $existingVM) -gt 0) {

      foreach ($vm in $existingVM) {

        DBG ('Will delete existing machine: {0} | {1} | isOff = {2}' -f $vm.ElementName, $vm.Name, ($vm.EnabledState -eq 3))

        [bool] $deleteBaseFolder = (Is-ValidString $customVM.baseFolder) -and ($customVM.baseFolder -eq (Get-VMxBaseFolder $vm $true))

        if ($vm.EnabledState -ne 3) {

          $retryCount = 10
          $retryDelay = 3
          $retryCounter = 1

          do {

            DBG ('Turn off the machine before deleting. Retry count: {0}' -f $retryCount)
            DBGSTART
            $res = $vm.RequestStateChange(3)
            DBGER $MyInvocation.MyCommand.Name $error
            DBGEND
  
            $jobRes = Wait-HyperVJob ([ref] $res)
            
            if ($jobRes.ErrorCode -eq 32775) {
             
              if ($retryCounter -lt $retryCount) {

                DBG ('Machine possibly in a transitional state, give it {0}sec. to complete and try again.' -f $retryDelay)
                Start-Sleep $retryDelay
                $retryCounter ++
                continue
              
              } else {

                DBG ('Retrials exhausted. Continue anyway.')
                break
              }
            
            } else {

              DBG ('Job finished with success or some non-retriable state. Continue')
              break
            }
          
          } while ($true)
        }

        DBG ('Close the VM connection windows if any opened yet')
        $vmWindows = Get-WMIQueryArray '.' ('SELECT * FROM Win32_Process WHERE Name = "vmconnect.exe" AND CommandLine LIKE "% \"{0}\" %\"{1}\"%"' -f $global:thisComputerHost, $customVM.name)
        
        foreach ($oneVmWindow in $vmWindows) {

          DBG ('One VM connection Windows to terminate: {0}' -f $oneVmWindow.CommandLine)
          DBGSTART
          $wmiRs = $null
          $wmiRs = $oneVmWindow.Terminate()
          DBGER $MyInvocation.MyCommand.Name $error
          DBGEND
          DBGWMI $wmiRs
        }

        DBG ('Delete the machine')

        $vmms = Get-VMxMS

        if ($global:virtualizationWmiAPIversion -eq 2) {

          DBGSTART
          $res = $vmms.DestroySystem($vm)
          DBGER $MyInvocation.MyCommand.Name $error
          DBGEND

        } else {

          DBGSTART
          $res = $vmms.DestroyVirtualSystem($vm)
          DBGER $MyInvocation.MyCommand.Name $error
          DBGEND
        }
  
        $jobRes = Wait-HyperVJob ([ref] $res)
        DBGIF $MyInvocation.MyCommand.Name { $jobRes.ErrorCode -ne 0 }
        # ignore results as well


        DBG ('Should we also delete its base folder: {0} | {1}' -f $deleteBaseFolder, $customVM.baseFolder)
        if (($deleteBaseFolder) -and (Is-ValidString $customVM.baseFolder)) {

          DBGIF $MyInvocation.MyCommand.Name { -not (Test-Path $customVM.baseFolder) }

          DBG ('Ensure there is no PERMANENT-* item inside: {0}' -f $customVM.baseFolder)
          DBGSTART
          $permanentItemsInBaseFolder = Get-ChildItem $customVM.baseFolder -Recurse -Force | select -Expand FullName | ? { $_ -like "*\$global:hypervPermanent" }
          DBGER $MyInvocation.MyCommand.Name $error
          DBGEND

          DBGIF ('Some PERMANENT-* items in the base folder: {0}' -f ($permanentItemsInBaseFolder -join ',')) { (Get-CountSafe $permanentItemsInBaseFolder) -gt 0 }
          
          if ((Get-CountSafe $permanentItemsInBaseFolder) -lt 1) {

            $retryCount = 5
            $retryDelay = 3
            $retryCounter = 1

            do {

              DBG ('Deleting the base folder. Retrial: {0}' -f $retryCounter)

              $lastError = $false
              DBGSTART
              Remove-Item $customVM.baseFolder -Recurse -Force -EA SilentlyContinue

              if ($error.Count -gt 0) {
             
                # Note: yes, I would rather like to populate the $lastError in the only cases
                #       of some specific errors such as the sharing violation, but the exception
                #       is totally unspecific and does not contain any error code
                $lastError = $true
                DBGER $MyInvocation.MyCommand.Name $error
                DBG ('The folder cannot be deleted due to some possibly transitional error. Retry #{0} in some {1} sec.' -f $retryCounter, $retryDelay)
                Start-Sleep $retryDelay
                $retryCounter ++
              }

              DBGEND

            } while ($lastError -and ($retryCounter -le $retryCount))

            DBGIF $MyInvocation.MyCommand.Name { $retryCounter -gt $retryCount }
          }
        }
      }
    }
  }
}


function global:Import-VMx ([string] $path, [string] $version, [ref] $dispoList)
{
  DBG ('{0}: Parameters: {1}' -f $MyInvocation.MyCommand.Name, (($PSBoundParameters.Keys | ? { $_ -ne 'object' } | % { '{0}={1}' -f $_, $PSBoundParameters[$_]}) -join $multivalueSeparator))

  $machine = $null

  DBGIF $MyInvocation.MyCommand.Name { -not (Test-Path $path) }
  DBGIF $MyInvocation.MyCommand.Name { $version -gt $global:thisOSVersion }
  
  if (Test-Path $path) {

    $importFolder = Split-Path (Split-Path $path -Parent) -Parent
    $snapshotFolder = Join-Path $importFolder 'Snapshots'
    $vhdFolder = Join-Path $importFolder 'Virtual Hard Disks'
    DBG ('Import path: {0}' -f $path)
    DBG ('Import folder path: {0}' -f $importFolder)
    DBG ('Import snapshots path: {0}' -f $snapshotFolder)
    DBG ('Import vhd path: {0}' -f $vhdFolder)

    if ($version -eq '6.1') {

      DBG ('Import VM with 6.1 logic')
      $vmms = Get-VMxMS

      DBG ('Load import setting data')
      DBGSTART
      $res = $vmms.GetVirtualSystemImportSettingData($importFolder)
      DBGER $MyInvocation.MyCommand.Name $error
      DBGEND

      $jobRes = Wait-HyperVJob ([ref] $res)

      if ($jobRes.ErrorCode -eq 0)
      {
        $importSettings = $res.ImportSettingData
        DBG ('Import setting data loaded: {0}' -f ($importSettings | Out-String))

        if (Is-NonNull $importSettings) {

          $importSettings.GenerateNewId = $false
          $importSettings.CreateCopy = $false

          DBG ('Import requires the following network switches: {0}' -f (Format-MultiValue $importSettings.SourceNetworkConnections))
          if ((Get-CountSafe $importSettings.SourceNetworkConnections) -gt 0) {

            # Note: imports are assumed to not require internal switches, or they get an APIPA address
            $importSettings.SourceNetworkConnections | % { Assert-VMxHyperVSwitch $_ | Out-Null }
          }

          DBG ('Import the VM')
          DBGSTART
          $res = $vmms.ImportVirtualSystemEx($importFolder, $importSettings.psbase.GetText('CimDtd20'))
          DBGER $MyInvocation.MyCommand.Name $error
          DBGEND

          $jobRes = Wait-HyperVJob ([ref] $res) 'DefinedSystem' 'Msvm_ComputerSystem'

          $machine = $res.DefinedSystem
          DBG ('Machine imported: {0}' -f $machine.__PATH)
        }
      }
        
    } else {

      DBG ('Import with 6.2+ logic')
      $vmms = Get-VMxMS

<#
TEST SAMPLES:

Import/validate a planned system

$vmms = Get-WmiObject MSVM_VirtualSystemManagementService -namespace $global:virtualizationNamespace
$vmms.ImportSystemDefinition('D:\TESTEXPORT\DC5-London\Virtual Machines\CB587BE0-1A02-45E2-B6C2-81A5B997F88D.XML', 'D:\TESTEXPORT\DC5-London\Snapshots', $false)
$plan = '\\TROTL\$($global:virtualizationNamespace):Msvm_PlannedComputerSystem.CreationClassName="Msvm_PlannedComputerSystem",Name="CB587BE0-1A02-45E2-B6C2-81A5B997F88D"'
$validJob = $vmms.ValidatePlannedSystem($plan).Job


Delete all unfinished planned systems

$vmms = Get-WmiObject MSVM_VirtualSystemManagementService -namespace $global:virtualizationNamespace
(gwmi  Msvm_PlannedComputerSystem -names $global:virtualizationNamespace) | % { $vmms.DestroySystem($_) }


Import settings on 2k8/r2 format

$vmms = gwmi Msvm_VirtualSystemManagementService -names $global:virtualizationNamespace
$res = $vmms.GetVirtualSystemImportSettingData('D:\TESTEXPORT\SP2010-WFE1')
$job = [wmi] $res.Job

$importSettings = $res.ImportSettingData
$importSettings.GenerateNewId = $false
$importSettings.CreateCopy = $false
$res = $vmms.ImportVirtualSystemEx('D:\TESTEXPORT\SP2010-WFE1', $importSettings.psbase.GetText('CimDtd20'))

$jobRes = Wait-HyperVJob ([ref] $res) 'DefinedSystem' 'MSVM_ComputerSystem'



#>

      DBG ('Import system definition into Msvm_PlannedComputerSystem')
      DBGSTART
      $res = $vmms.ImportSystemDefinition($path, $snapshotPath, $false)
      DBGER $MyInvocation.MyCommand.Name $error
      DBGEND

      $jobRes = Wait-HyperVJob ([ref] $res) 'ImportedSystem' 'Msvm_PlannedComputerSystem'

      if ($jobRes.ErrorCode -eq 0)
      {
        $importSettings = [WMI] $res.ImportedSystem
        DBG ('Imported/planned setting data loaded: {0}' -f ($importSettings | Out-String))

        if (Is-NonNull $importSettings) {

          DBG ('Fix VHD paths for the planned computer system')

          $importSettingData = Get-WMIRelated $importSettings 'Msvm_VirtualSystemSettingData'
          DBG ('Found import settings data for VM/snapshots: {0}' -f (Get-CountSafe $importSettingData))

          if ((Get-CountSafe $importSettingData) -gt 0) {

            $importSettingData | % {

              $oneISD = $_
              DBG ('Import setting data: {0} | {1} | isSnapshot = {2}' -f $oneISD.ElementName, $oneISD.Description, (Is-ValidString $oneISD.Parent))

              $plannedVHDs = (Get-WMIRelated $oneISD 'Msvm_StorageAllocationSettingData') | ? { ($_.ResourceType -eq 31) -and ($_.ResourceSubType -eq 'Microsoft:Hyper-V:Virtual Hard Disk') }
              DBG ('Found VHDs on these setting data: {0}' -f (Get-CountSafe $plannedVHDs))

              if ((Get-CountSafe $plannedVHDs) -gt 0) {

                $plannedVHDs | % {

                  $origVHD = $_.HostResource[0]
                  $origVHDfile = Split-Path -Path $origVHD -Leaf
                  DBG ('Planned VHD original path: {0} | {1}' -f $origVHD, $origVHDfile)

                  $newVHD = Join-Path $vhdFolder $origVHDfile
                  DBG ('Will replace with new VHD path: {0}' -f $newVHD)

                  DBG ('Update VHD path with call to ModifyResourceSettings()')
                  DBGSTART
                  $_.HostResource = @($newVHD)
                  $res = $vmms.ModifyResourceSettings($_.GetText(1))
                  DBGER $MyInvocation.MyCommand.Name $error
                  DBGEND

                  $jobRes = Wait-HyperVJob ([ref] $res)
                }
              }
            }
          }

          DBG ('Validate the VM import settings first')
          DBGSTART
          $res = $vmms.ValidatePlannedSystem($importSettings)
          DBGER $MyInvocation.MyCommand.Name $error
          DBGEND

          $jobRes = Wait-HyperVJob ([ref] $res)

          DBG ('Import the VM')
          DBGSTART
          $res = $vmms.RealizePlannedSystem($importSettings)
          DBGER $MyInvocation.MyCommand.Name $error
          DBGEND

          $jobRes = Wait-HyperVJob ([ref] $res) DefinedSystem 'Msvm_ComputerSystem'

          $machine = $res.DefinedSystem
          DBG ('Machine imported: {0}' -f $machine.__PATH)
        }
      }      
    }
  }

  if (Is-NonNull $dispoList) {

    [void] $dispoList.Value.Add($machine)
  }

  return $machine
}


function global:Create-NewVM ([object] $vmSpec, [ref] $dispoList)
{
  DBG ('{0}: Parameters: {1}' -f $MyInvocation.MyCommand.Name, (($PSBoundParameters.Keys | ? { $_ -ne 'object' } | % { '{0}={1}' -f $_, $PSBoundParameters[$_]}) -join $multivalueSeparator))

  #$settings = ([WMIClass] "\\.\$($global:virtualizationNamespace):Msvm_VirtualSystemGlobalSettingData").CreateInstance()
  #$settings.psbase.Properties.Item("ElementName").value = $machineName
  #$settings.psbase.Properties.Item("ExternalDataRoot").value = $_.baseFolder
  #$settings.psbase.Properties.Item("SnapshotDataRoot").value = $_.baseFolder

  [wmi] $newMachine = $null

  if ((Is-NonNull $vmSpec) -and (Is-ValidString $vmSpec.baseFolder) -and (Test-Path $vmSpec.baseFolder)) {

    if ($global:virtualizationWmiAPIversion -eq 1) {

      DBG ('Create VM with v1 API')
      $settings = Spawn-WMIInstance '.' 'Msvm_VirtualSystemGlobalSettingData' $global:virtualizationNamespace
  
      switch ($vmSpec.shutdown) {

        'turnoff'  { $shutdownInt = 0 }
        'save'     { $shutdownInt = 1 }
        'shutdown' { $shutdownInt = 2 }
        default    { $shutdownInt = -1 }
      }

      switch ($vmSpec.startup) {

        'none'     { $startupInt = 0 }
        'previous' { $startupInt = 1 }
        'always'   { $startupInt = 2 }
        default    { $startupInt = -1 }
      }

      DBGSTART
      $settings.ElementName = $vmSpec.name
      $settings.ExternalDataRoot = $vmSpec.baseFolder
      $settings.SnapshotDataRoot = $vmSpec.baseFolder

      if ($startupInt -ge 0) {

        $settings.AutomaticStartupAction = $startupInt
      }

      if ($vmSpec.startDelay -gt 0) {

        $settings.AutomaticStartupActionDelay = [System.Management.ManagementDateTimeconverter]::ToDmtfTimeInterval((New-TimeSpan -Seconds $vmSpec.startDelay))
        DBG ('Setting startup delay: {0} | {1}' -f $settings.AutomaticStartupActionDelay, $vmSpec.startDelay)
      }

      if ($shutdownInt -ge 0) {

        $settings.AutomaticShutdownAction = $shutdownInt
      }

      $res = $vmms.DefineVirtualSystem($settings.GetText(1))
      DBGER $MyInvocation.MyCommand.Name $error
      DBGEND

      $jobRes = Wait-HyperVJob ([ref] $res) 'DefinedSystem' 'MSVM_ComputerSystem'
      if ($jobRes.ErrorCode -eq 0)
      {
        $newMachine = [wmi] $res.DefinedSystem
      }

    } else {

      DBG ('Create VM with v2 API')
      $settings = Spawn-WMIInstance '.' 'Msvm_VirtualSystemSettingData' $global:virtualizationNamespace
  
      switch ($vmSpec.shutdown) {

        'turnoff'  { $shutdownInt = 2 }
        'save'     { $shutdownInt = 3 }
        'shutdown' { $shutdownInt = 4 }
        default    { $shutdownInt = -1 }
      }

      switch ($vmSpec.startup) {

        'none'     { $startupInt = 2 }
        'previous' { $startupInt = 3 }
        'always'   { $startupInt = 4 }
        default    { $startupInt = -1 }
      }

      DBGSTART
      $settings.ElementName = $vmSpec.name
      $settings.ConfigurationDataRoot = $vmSpec.baseFolder
      $settings.SnapshotDataRoot = $vmSpec.baseFolder
      $settings.SuspendDataRoot = $vmSpec.baseFolder
      $settings.SwapFileDataRoot = $vmSpec.baseFolder
      $settings.LogDataRoot = $vmSpec.baseFolder

      
      #$settings.VirtualSystemSubType = 'Microsoft:Hyper-V:SubType:1'

      if ($vmSpec.gen -eq 2) {

        DBG ('Creating generation 2 (gen2, gen 2) virtual machine: {0}' -f $vmSpec.secBoot)
        $settings.VirtualSystemSubType = 'Microsoft:Hyper-V:SubType:2'
        $settings.SecureBootEnabled = -not $vmSpec.noSecBoot
      }

      if ($startupInt -ge 0) {

        $settings.AutomaticStartupAction = $startupInt
      }

      if ($vmSpec.startDelay -gt 0) {

        $settings.AutomaticStartupActionDelay = [System.Management.ManagementDateTimeconverter]::ToDmtfTimeInterval((New-TimeSpan -Seconds $vmSpec.startDelay))
        DBG ('Setting startup delay: {0} | {1}' -f $settings.AutomaticStartupActionDelay, $vmSpec.startDelay)
      }

      if ($shutdownInt -ge 0) {

        $settings.AutomaticShutdownAction = $shutdownInt
      }

      $res = $vmms.DefineSystem($settings.GetText(1))
      DBGER $MyInvocation.MyCommand.Name $error
      DBGEND

      $jobRes = Wait-HyperVJob ([ref] $res) 'DefinedSystem' 'MSVM_ComputerSystem'
      if ($jobRes.ErrorCode -eq 0)
      {
        $newMachine = [wmi] $res.ResultingSystem
      }
    }
  }

  DBG ('Created VM: {0} | {1}' -f $newMachine.ElementName, $newMachine.Name)
  DBGIF $MyInvocation.MyCommand.Name { Is-Null $newMachine }

  if (Is-NonNull $newMachine) {

    DBG ('Allow the virtual machine access to its own base folder: {0} | {1}' -f $vmSpec.baseFolder, $newMachine.Name)
    Run-Process icacls ('"{0}" /reset /T /C /L' -f $vmSpec.baseFolder)
    Run-Process icacls ('"{0}" /grant:r "NT VIRTUAL MACHINE\{1}:(OI)(CI)F"' -f $vmSpec.baseFolder, $newMachine.Name)
  }

  if (Is-NonNull $dispoList) {

    [void] $dispoList.Value.Add($newMachine)
  }

  return $newMachine 
}


function global:New-CustomVM (
    [string] $opType, 
    [string] $name, 
    [string] $id,
    [bool] $do, 
    [string] $path,
    [string] $version,
    [string] $isRunning,
    [string] $vhd,
    [string] $unatt, 
    [string] $iso, 
    [string] $mem, 
    [string] $cpu, 
    [string] $nics,
    [string] $timeSync,
    [string] $numLock,
    [string] $autoStart,
    [bool] $remove,
    [string] $adminPwd,
    [string] $pk,
    [string] $org,
    [string] $image,
    [string] $baseFolder,
    [string] $shutdown,
    [string] $startup,
    [int] $startDelay,
    [bool] $waitToFinish,
    [bool] $vfdRequired = $false,
    [int] $gen,
    [bool] $noSecBoot,
    [bool] $tpm,
    [bool] $nestedVMs,
    [bool] $deliver
    )
# $type - buildup, import, existing
# most must be [string] because we sometimes have bool values such as "-" to mark "do not touch" especially during import
{
  DBG ('{0}: Parameters: {1}' -f $MyInvocation.MyCommand.Name, (($PSBoundParameters.Keys | ? { $_ -ne 'object' } | % { '{0}={1}' -f $_, $PSBoundParameters[$_]}) -join $multivalueSeparator))

  DBGIF $MyInvocation.MyCommand.Name { Is-EmptyString $opType }
  DBGIF $MyInvocation.MyCommand.Name { ($opType -ne 'buildup') -and ($opType -ne 'import') -and ($opType -ne 'existing') }
  DBGIF $MyInvocation.MyCommand.Name { Is-EmptyString $name }

  $newVM = New-Object PSCustomObject

  $newVM | Add-Member -MemberType NoteProperty -Name opType -Value $opType
  $newVM | Add-Member -MemberType NoteProperty -Name name -Value $name
  $newVM | Add-Member -MemberType NoteProperty -Name id -Value $id


  # Note: the state of the DO fields means the final state of the virtual environment
  #       which is also the setting which the inner BUILDERs concern and act according to
  #       While the DELIVER field is the sole responsibility of the BREEDER component
  #       This means that the DO field will stay true even in such example cases as:
  #          user asked to not build the VM manually
  #          user is rebuilding only some VMs
  #          this cluster node does not build the VM because is should not be placed here
  $newVM | Add-Member -MemberType NoteProperty -Name do -Value $do
  $newVM | Add-Member -MemberType NoteProperty -Name deliver -Value $deliver


  $newVM | Add-Member -MemberType NoteProperty -Name path -Value $path # import only
  $newVM | Add-Member -MemberType NoteProperty -Name version -Value $version # import only
  $newVM | Add-Member -MemberType NoteProperty -Name isRunning -Value $isRunning # existing only
  $newVM | Add-Member -MemberType NoteProperty -Name vhd -Value $vhd
  $newVM | Add-Member -MemberType NoteProperty -Name unatt -Value $unatt
  $newVM | Add-Member -MemberType NoteProperty -Name iso -Value $iso
  $newVM | Add-Member -MemberType NoteProperty -Name mem -Value $mem
  $newVM | Add-Member -MemberType NoteProperty -Name cpu -Value $cpu
  $newVM | Add-Member -MemberType NoteProperty -Name nics -Value $nics
  $newVM | Add-Member -MemberType NoteProperty -Name timeSync -Value $timeSync
  $newVM | Add-Member -MemberType NoteProperty -Name numLock -Value $numLock
  $newVM | Add-Member -MemberType NoteProperty -Name autoStart -Value $autoStart
  $newVM | Add-Member -MemberType NoteProperty -Name remove -Value $remove
  $newVM | Add-Member -MemberType NoteProperty -Name adminPwd -Value $adminPwd
  $newVM | Add-Member -MemberType NoteProperty -Name pk -Value $pk
  $newVM | Add-Member -MemberType NoteProperty -Name org -Value $org
  $newVM | Add-Member -MemberType NoteProperty -Name image -Value $image
  $newVM | Add-Member -MemberType NoteProperty -Name baseFolder -Value $baseFolder # existing only
  $newVM | Add-Member -MemberType NoteProperty -Name shutdown -Value $shutdown
  $newVM | Add-Member -MemberType NoteProperty -Name startup -Value $startup
  $newVM | Add-Member -MemberType NoteProperty -Name startDelay -Value $startDelay
  $newVM | Add-Member -MemberType NoteProperty -Name waitToFinish -Value $waitToFinish
  $newVM | Add-Member -MemberType NoteProperty -Name finished -Value $false
  $newVM | Add-Member -MemberType NoteProperty -Name finishedStages -Value @{}
  $newVM | Add-Member -MemberType NoteProperty -Name buildStarted -Value ([Datetime]::Now)
  $newVM | Add-Member -MemberType NoteProperty -Name buildFinished -Value $null
  $newVM | Add-Member -MemberType NoteProperty -Name buildOverall -Value 0
  $newVM | Add-Member -MemberType NoteProperty -Name exists -Value $false # notes the fact for buildup/import whether the machine name already exists in Hyper-V
  $newVM | Add-Member -MemberType NoteProperty -Name vfdRequired -value $vfdRequired
  $newVM | Add-Member -MemberType NoteProperty -Name gen -value $gen
  $newVM | Add-Member -MemberType NoteProperty -Name noSecBoot -value $noSecBoot
  $newVM | Add-Member -MemberType NoteProperty -Name tpm -value $tpm
  $newVM | Add-Member -MemberType NoteProperty -Name nestedVMs -value $nestedVMs

  return $newVM
}


function global:Get-VMxKVPs ([WMI] $vm)
{
  DBG ('{0}: Parameters: {1}' -f $MyInvocation.MyCommand.Name, (($PSBoundParameters.Keys | ? { $_ -ne 'object' } | % { '{0}={1}' -f $_, $PSBoundParameters[$_]}) -join $multivalueSeparator))

  DBGIF $MyInvocation.MyCommand.Name { Is-Null $vm }
  DBGIF $MyInvocation.MyCommand.Name { $global:thisOSVersionNumber -lt 6.2 }

  [hashtable] $gotValues = @{}
  [Collections.ArrayList] $dispoList = @()

  if (Is-NonNull $vm) {

    $kvp = Get-WmiRelated $vm Msvm_KvpExchangeComponent
    [void] $dispoList.AddRange($kvp)
    DBGIF $MyInvocation.MyCommand.Name { Is-Null $kvp }

    $kvpValues = $kvp.GuestExchangeItems
    DBG ('Key-value pairs obtained: {0}' -f (Get-CountSafe $kvpValues))

    if ((Is-NonNull $kvp) -and ((Get-CountSafe $kvpValues) -gt 0)) {

      foreach ($oneKvpValue in $kvpValues) {

        $kvpValueXml = [xml] $oneKvpValue
        $kvpValueName = $kvpValueXml.SelectSingleNode('/INSTANCE[@CLASSNAME="Msvm_KvpExchangeDataItem"]/PROPERTY[@NAME="Name"]/VALUE').InnerText
        $kvpValueData = $kvpValueXml.SelectSingleNode('/INSTANCE[@CLASSNAME="Msvm_KvpExchangeDataItem"]/PROPERTY[@NAME="Data"]/VALUE').InnerText

        DBG ('One KVP value: {0} | {1}' -f $kvpValueName, $kvpValueData)
        $gotValues.Add($kvpValueName, $kvpValueData)
      }
    }
  }

  Dispose-List ([ref] $dispoList)

  DBG ('Returning KVPs: {0}' -f (Get-CountSafe $gotValues.Keys))
  return $gotValues
}


function global:Assert-BuilderLocalGuardian ([bool] $enableLocal, [bool] $reset)
{
  DBG ('{0}: Parameters: {1}' -f $MyInvocation.MyCommand.Name, (($PSBoundParameters.Keys | ? { $_ -ne 'object' } | % { '{0}={1}' -f $_, $PSBoundParameters[$_]}) -join $multivalueSeparator))

  DBGIF $MyInvocation.MyCommand.Name { $global:thisOSVersionNumber -lt 10 }
  if ($global:thisOSVersionNumber -lt 10) {

    return $null
  }

  DBG ('Obtain HGS client configuration')
  DBGSTART
  $hgsClt = Get-HgsClientConfiguration    
  DBGER $MyInvocation.MyCommand.Name $error
  DBGEND

  DBGIF $MyInvocation.MyCommand.Name { $hgsClt.Mode -ne 'Local' }
  if ($hgsClt.Mode -ne 'Local') {

    if ($enableLocal) {

      DBG ('The HGS client mode is not local, switching to Local mode')
      DBGSTART
      Set-HgsClientConfiguration -EnableLocalMode
      DBGER $MyInvocation.MyCommand.Name $error
      DBGEND

    } else {

      DBG ('Cannot continue. The HGS client mode is not Local. Use the -enableLocal parameter to reset the HGS client to Local mode')
      return $null
    }
  }

  if ($reset) {

    $guardianRoot = 'HKLM:\Software\Microsoft\HgsClient'
    $guardianList = 'HKLM:\Software\Microsoft\HgsClient\Guardians'
    DBG ('User asked for forcible guardian reset: {0} | exists = {1}' -f $guardianList, (Test-Path $guardianList))

    if (Test-Path $guardianList) {

      DBG ('Remove any existing guardians')
      DBGSTART
      (Get-Item $guardianList).Property | % { DBG ('Removing one guardian from registry: {0}' -f $_); Remove-ItemProperty $guardianList -Name $_ -Force }
      DBGER $MyInvocation.MyCommand.Name $error
      DBGEND
    }

    if (Test-Path $guardianRoot) {

      DBG ('Reset other local parameters')
      DBGSTART
      $guardianRootReg = Get-Item $guardianRoot
      DBGER $MyInvocation.MyCommand.Name $error
      DBGEND
      DBG ('Properties found: {0}' -f ($guardianRootReg.Property -join ','))

      if ($guardianRootReg.Property -contains 'KdsService') {

        DBG ('Removing current HGS client value: {0} | {1}' -f 'KdsService', (Get-ItemProperty $guardianRoot KdsService | Select -Expand KdsService))
        DBGSTART
        Remove-ItemProperty $guardianRoot KdsService
        DBGER $MyInvocation.MyCommand.Name $error
        DBGEND
      }

      if ($guardianRootReg.Property -contains 'RemoteAttestationService') {

        DBG ('Removing current HGS client value: {0} | {1}' -f 'RemoteAttestationService', (Get-ItemProperty $guardianRoot RemoteAttestationService | Select -Expand RemoteAttestationService))
        DBGSTART
        Remove-ItemProperty $guardianRoot RemoteAttestationService
        DBGER $MyInvocation.MyCommand.Name $error
        DBGEND
      }

      DBG ('Verify the local HGS client parameters')
      DBGSTART
      $guardianRootReg = Get-Item $guardianRoot
      DBGER $MyInvocation.MyCommand.Name $error
      DBGEND
      DBGIF ('Some other unknown local configuration parameters found: {0}' -f ($guardianRootReg.Property -join ',')) { ((Get-CountSafe $guardianRootReg.Property) -ne 1) -or ($guardianRootReg.Property -notcontains 'Mode') }
    }
  }

  DBG ('Obtain all HGS guardians')
  DBGSTART
  $guardians = Get-HgsGuardian
  DBGER $MyInvocation.MyCommand.Name $error
  DBGEND
  DBG ('Obtained guardians: #{0} | {1}' -f (Get-CountSafe $guardians), (($guardians | select -Expand Name) -join ','))

  $sevecekBldrGuardian = 'SevecekBldrLocalGuardian'

  if (-not (Contains-Safe $guardians $sevecekBldrGuardian Name)) {

    DBG ('The builder local guardian does not exist, create one')
    DBGSTART
    $guardian = New-HgsGuardian -Name $sevecekBldrGuardian -GenerateCertificates
    DBGER $MyInvocation.MyCommand.Name $error
    DBGEND
    DBGIF $MyInvocation.MyCommand.Name { Is-Null $guardian }
  
  } else {

    $guardian = $guardians | ? { $_.Name -eq $sevecekBldrGuardian }
  }

  DBGIF $MyInvocation.MyCommand.Name { Is-Null (dir 'Cert:\LocalMachine\Shielded VM Local Certificates\' | ? { $_.Thumbprint -eq $guardian.SigningCertificate.Thumbprint }) }
  DBGIF $MyInvocation.MyCommand.Name { Is-Null (dir 'Cert:\LocalMachine\Shielded VM Local Certificates\' | ? { $_.Thumbprint -eq $guardian.EncryptionCertificate.Thumbprint }) }

  DBG ('Making the builder guardian trusted')
  DBGSTART
  $rootStore = New-Object System.Security.Cryptography.X509Certificates.X509Store ('Root', 'LocalMachine')
  $rootStore.Open('MaxAllowed')
  $rootStore.Add($guardian.SigningCertificate) 
  $rootStore.Add($guardian.EncryptionCertificate) 
  $rootStore.Close()
  DBGER $MyInvocation.MyCommand.Name $error
  DBGEND

  DBG ('Returning the asserted guardian: {0}' -f $guardian.Name)
  DBGIF $MyInvocation.MyCommand.Name { Is-Null $guardian }

  return $guardian
}


function global:Start-VMx ([WMI] $vm)
{
  DBG ('{0}: Parameters: {1}' -f $MyInvocation.MyCommand.Name, (($PSBoundParameters.Keys | ? { $_ -ne 'object' } | % { '{0}={1}' -f $_, $PSBoundParameters[$_]}) -join $multivalueSeparator))

  DBG ('Starting the machine: {0} | {1}' -f $vm.ElementName, $vm.Name)
  DBGSTART
  $res = $vm.RequestStateChange(2)
  DBGER $MyInvocation.MyCommand.Name $error
  DBGEND
  
  $jobRes = Wait-HyperVJob ([ref] $res)
}


function global:Reboot-VMx ([WMI] $vm)
{
  DBG ('{0}: Parameters: {1}' -f $MyInvocation.MyCommand.Name, (($PSBoundParameters.Keys | ? { $_ -ne 'object' } | % { '{0}={1}' -f $_, $PSBoundParameters[$_]}) -join $multivalueSeparator))

  if ($global:thisOSVersionNumber -ge 6.2) {

    DBG ('Rebooting the machine with V2: {0} | {1}' -f $vm.ElementName, $vm.Name)
    DBGSTART
    $res = $vm.RequestStateChange(11)
    DBGER $MyInvocation.MyCommand.Name $error
    DBGEND
    $jobRes = Wait-HyperVJob ([ref] $res)

  } else {
  
    DBG ('Rebooting the machine with V1: {0} | {1}' -f $vm.ElementName, $vm.Name)
    DBGSTART
    $res = $vm.RequestStateChange(10)
    DBGER $MyInvocation.MyCommand.Name $error
    DBGEND
    $jobRes = Wait-HyperVJob ([ref] $res)
  }
}


function global:Mount-ISO (
  [string] $isoPathWildcard,
  [string] $driveLetter
  )
{
  DBG ('{0}: Parameters: {1}' -f $MyInvocation.MyCommand.Name, (($PSBoundParameters.Keys | ? { $_ -ne 'object' } | % { '{0}={1}' -f $_, $PSBoundParameters[$_]}) -join $multivalueSeparator))

  $isoPath = Get-Item $isoPathWildcard | select -Expand FullName | sort -Descending | select -First 1
  DBG ('ISO to mount determined: {0}' -f $isoPath)
  DBGIF $MyInvocation.MyCommand.Name { Is-EmptyString $isoPath }

  $driveLetterNormalized = '{0}:' -f $driveLetter[0]
  DBG ('The drive letter to mount normalized: {0}' -f $driveLetterNormalized)
  DBGIF $MyInvocation.MyCommand.Name { (Is-EmptyString $driveLetterNormalized) -or ($driveLetterNormalized -notlike '[a-z]:') -or (Test-Path $driveLetterNormalized) }

  if ((Is-ValidString $isoPath) -and (Is-ValidString $driveLetterNormalized) -and ($driveLetterNormalized -like '[a-z]:') -and (-not (Test-Path $driveLetterNormalized))) {

    DBG ('Mount the image: {0}' -f $isoPath)
    DBGSTART
    $mountedISO = Mount-DiskImage -ImagePath $isoPath -NoDriveLetter
    DBGER $MyInvocation.MyCommand.Name $error
    DBGEND

    DBG ('Get the mounted volume')
    DBGSTART
    $isoVolume = $null
    $isoVolume = Get-Volume -DiskImage $mountedISO
    DBGER $MyInvocation.MyCommand.Name $error
    DBGEND
    DBGIF $MyInvocation.MyCommand.Name { Is-Null $isoVolume }

    if (Is-NonNull $isoVolume) {

      $isoWmiVolume = Get-WmiQuerySingleObject '.' ('SELECT * FROM Win32_Volume WHERE Name = "{0}"' -f $isoVolume.UniqueId.Replace('\', '\\'))
      DBGIF $MyInvocation.MyCommand.Name { Is-Null $isoWmiVolume }

      if (Is-NonNull $isoWmiVolume) {

        DBG ('Assign the drive letter: {0}' -f $driveLetterNormalized)
        DBGSTART
        $isoWmiVolume.DriveLetter = $driveLetterNormalized
        [void] $isoWmiVolume.Put()
        DBGER $MyInvocation.MyCommand.Name $error
        DBGEND
      }
    }
  }
}


function global:Get-MountedISO ([string] $isoPath)
{
  DBG ('{0}: Parameters: {1}' -f $MyInvocation.MyCommand.Name, (($PSBoundParameters.Keys | ? { $_ -ne 'object' } | % { '{0}={1}' -f $_, $PSBoundParameters[$_]}) -join $multivalueSeparator))
  
  [string] $driveLetter = $null

  DBGIF $MyInvocation.MyCommand.Name { -not (Test-Path $isoPath) }

  if (Test-Path $isoPath) {
  
    DBG ('Get the disk image parameters')
    DBGSTART
    $mountedDisk = Get-DiskImage $isoPath
    DBGER $MyInvocation.MyCommand.Name $error
    DBGEND
    DBGIF $MyInvocation.MyCommand.Name { Is-Null $mountedDisk }

    if (Is-NonNull $mountedDisk) {

      DBG ('Obtain the mounted disk volume')
      DBGSTART
      $mountedVolume = Get-Volume -DiskImage $mountedDisk
      DBGER $MyInvocation.MyCommand.Name $error
      DBGEND

      if (Is-NonNull $mountedVolume) {

        $driveLetter = '{0}:' -f $mountedVolume.DriveLetter[0]
      }
    }
  }  

  DBG ('The ISO is mounted at: {0} | {1}' -f $driveLetter, $isoPath)
  return $driveLetter
}


function global:Dismount-AllISOs
{
  DBG ('{0}: Parameters: {1}' -f $MyInvocation.MyCommand.Name, (($PSBoundParameters.Keys | ? { $_ -ne 'object' } | % { '{0}={1}' -f $_, $PSBoundParameters[$_]}) -join $multivalueSeparator))


}


# SIG # Begin signature block
# MIIYMAYJKoZIhvcNAQcCoIIYITCCGB0CAQExDzANBglghkgBZQMEAgEFADB5Bgor
# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG
# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCCyVm4PMHjTO86e
# RWPH/LXkZBVYbxZlhI8vl7Jl/GdAh6CCE0cwggYEMIID7KADAgECAgoqHIRwAAEA
# AAB/MA0GCSqGSIb3DQEBCwUAMGwxCzAJBgNVBAYTAkNaMRcwFQYDVQQIEw5DemVj
# aCBSZXB1YmxpYzENMAsGA1UEBxMEQnJubzEQMA4GA1UEChMHU2V2ZWNlazEjMCEG
# A1UEAxMaU2V2ZWNlayBFbnRlcnByaXNlIFJvb3QgQ0EwHhcNMTkwNjExMTkyMzMy
# WhcNMjQwNjA5MTkyMzMyWjCBjzELMAkGA1UEBhMCQ1oxFzAVBgNVBAgTDkN6ZWNo
# IFJlcHVibGljMQ0wCwYDVQQHEwRCcm5vMRwwGgYDVQQKExNJbmcuIE9uZHJlaiBT
# ZXZlY2VrMRcwFQYDVQQDEw5PbmRyZWogU2V2ZWNlazEhMB8GCSqGSIb3DQEJARYS
# b25kcmVqQHNldmVjZWsuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC
# AQEAnkjWNkK4FfUUN8iAN91ry+wsSn8cFKJbMnROAqTrx8t3H315p2/bUG2DosCF
# Odu0WcaTOLdm5obhT+/3O7BqpdcnlWKlSEz4AL9zQeCbe4++NObBVPBbPE16j9C4
# xELoXW/Ti86C2PEkN5azGUvxGxzQQ45g32OsEI+Bh05qHMkk3oQ6L8O0Fpd5W4e+
# L4HuKS3JOikNhhryTNPD9grF/0wXTzn94TrL1GohuaCPh8g9HOtMoDCd+ExnqV8q
# 4k60D37BOK1I81hYFIBn8MvCsjMRC5TK87MtI7aUUIeve5kopc8ZpxNti3F/+Puh
# 4UUxL3nKjfAM6HE0b7FqkfkRpwIDAQABo4IBgjCCAX4wEwYDVR0lBAwwCgYIKwYB
# BQUHAwMwDgYDVR0PAQH/BAQDAgbAMBsGCSsGAQQBgjcVCgQOMAwwCgYIKwYBBQUH
# AwMwHQYDVR0OBBYEFOKbNkkiAht2GxCISJMJxLg4gOC9MB0GA1UdEQQWMBSBEm9u
# ZHJlakBzZXZlY2VrLmNvbTAfBgNVHSMEGDAWgBQNnMgyfdUi8l9UfithS4FQ88Vs
# wDBSBgNVHR8ESzBJMEegRaBDhkFodHRwOi8vcGtpLnNldmVjZWsuY29tL0NBL1Nl
# dmVjZWslMjBFbnRlcnByaXNlJTIwUm9vdCUyMENBKDEpLmNybDCBhgYIKwYBBQUH
# AQEEejB4ME0GCCsGAQUFBzAChkFodHRwOi8vcGtpLnNldmVjZWsuY29tL0NBL1Nl
# dmVjZWslMjBFbnRlcnByaXNlJTIwUm9vdCUyMENBKDEpLmNydDAnBggrBgEFBQcw
# AYYbaHR0cDovL3BraS5zZXZlY2VrLmNvbS9vY3NwMA0GCSqGSIb3DQEBCwUAA4IC
# AQCfr6XDtt/O8OBr+X5l49UBLaJrjUXHkAHofdC7p7BLCXIs4GYIti1lf6pas5yB
# Q428aKITITq/vEHUTyiiyKtzVkafILWXXKPxy+zmmuw9odB3Hea4ECNpcaG8UNtz
# vMm1Dr0ZrkENhcv6I3tNhRr2AOE9AKOfnVEullFD/mZqfmaNkhpnl31jk7OMSUQc
# oY8qD6BDQP9371C10gJOmp57sHfPa4Vn5E4aNzn8+o9C9HI7nNagZF5BamKOFdR2
# ui7K3krMbTuDHo+ZcA9nHnzZqiVKpEBFu8lGv9Mf+GDb9yxz6EjV3xS2RcnywX2v
# z0VUt2NGno8LudrnWpgrRy4Sl7x6FwVVKtS/o7zFSIiHgntIKFv8urSKSTukCLFK
# Y9fBIDDlWFV1ZV1DNpNWxnexWIRv2AH7YlzKQCA4Rysn01hVeBGsWFkCr9J33LmV
# enQYpk9eoYMPRwAYg48r65wOOOzLvmyLSGllH88BMvmTQ9myXqwp6NDH1psljXTl
# PUbpf7w6IZwsY0dhGhP9iyqbcrGdK0Bnf8Za6Qdj3iXtwd1VgpatFZrxOM5KawCL
# pkYl1ABupbzNpWzmC+nfymqwbYiCogPt1vHOyF4EJ73ExVDCqXkpiNvFRqmu1eaZ
# IOdbPCdl00a9rk52NKqo/BUsw16TKsDEYTA/7ACbEsnERzCCBmowggVSoAMCAQIC
# EAMBmgI6/1ixa9bV6uYX8GYwDQYJKoZIhvcNAQEFBQAwYjELMAkGA1UEBhMCVVMx
# FTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZMBcGA1UECxMQd3d3LmRpZ2ljZXJ0LmNv
# bTEhMB8GA1UEAxMYRGlnaUNlcnQgQXNzdXJlZCBJRCBDQS0xMB4XDTE0MTAyMjAw
# MDAwMFoXDTI0MTAyMjAwMDAwMFowRzELMAkGA1UEBhMCVVMxETAPBgNVBAoTCERp
# Z2lDZXJ0MSUwIwYDVQQDExxEaWdpQ2VydCBUaW1lc3RhbXAgUmVzcG9uZGVyMIIB
# IjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAo2Rd/Hyz4II14OD2xirmSXU7
# zG7gU6mfH2RZ5nxrf2uMnVX4kuOe1VpjWwJJUNmDzm9m7t3LhelfpfnUh3SIRDsZ
# yeX1kZ/GFDmsJOqoSyyRicxeKPRktlC39RKzc5YKZ6O+YZ+u8/0SeHUOplsU/UUj
# joZEVX0YhgWMVYd5SEb3yg6Np95OX+Koti1ZAmGIYXIYaLm4fO7m5zQvMXeBMB+7
# NgGN7yfj95rwTDFkjePr+hmHqH7P7IwMNlt6wXq4eMfJBi5GEMiN6ARg27xzdPpO
# 2P6qQPGyznBGg+naQKFZOtkVCVeZVjCT88lhzNAIzGvsYkKRrALA76TwiRGPdwID
# AQABo4IDNTCCAzEwDgYDVR0PAQH/BAQDAgeAMAwGA1UdEwEB/wQCMAAwFgYDVR0l
# AQH/BAwwCgYIKwYBBQUHAwgwggG/BgNVHSAEggG2MIIBsjCCAaEGCWCGSAGG/WwH
# ATCCAZIwKAYIKwYBBQUHAgEWHGh0dHBzOi8vd3d3LmRpZ2ljZXJ0LmNvbS9DUFMw
# ggFkBggrBgEFBQcCAjCCAVYeggFSAEEAbgB5ACAAdQBzAGUAIABvAGYAIAB0AGgA
# aQBzACAAQwBlAHIAdABpAGYAaQBjAGEAdABlACAAYwBvAG4AcwB0AGkAdAB1AHQA
# ZQBzACAAYQBjAGMAZQBwAHQAYQBuAGMAZQAgAG8AZgAgAHQAaABlACAARABpAGcA
# aQBDAGUAcgB0ACAAQwBQAC8AQwBQAFMAIABhAG4AZAAgAHQAaABlACAAUgBlAGwA
# eQBpAG4AZwAgAFAAYQByAHQAeQAgAEEAZwByAGUAZQBtAGUAbgB0ACAAdwBoAGkA
# YwBoACAAbABpAG0AaQB0ACAAbABpAGEAYgBpAGwAaQB0AHkAIABhAG4AZAAgAGEA
# cgBlACAAaQBuAGMAbwByAHAAbwByAGEAdABlAGQAIABoAGUAcgBlAGkAbgAgAGIA
# eQAgAHIAZQBmAGUAcgBlAG4AYwBlAC4wCwYJYIZIAYb9bAMVMB8GA1UdIwQYMBaA
# FBUAEisTmLKZB+0e36K+Vw0rZwLNMB0GA1UdDgQWBBRhWk0ktkkynUoqeRqDS/Qe
# icHKfTB9BgNVHR8EdjB0MDigNqA0hjJodHRwOi8vY3JsMy5kaWdpY2VydC5jb20v
# RGlnaUNlcnRBc3N1cmVkSURDQS0xLmNybDA4oDagNIYyaHR0cDovL2NybDQuZGln
# aWNlcnQuY29tL0RpZ2lDZXJ0QXNzdXJlZElEQ0EtMS5jcmwwdwYIKwYBBQUHAQEE
# azBpMCQGCCsGAQUFBzABhhhodHRwOi8vb2NzcC5kaWdpY2VydC5jb20wQQYIKwYB
# BQUHMAKGNWh0dHA6Ly9jYWNlcnRzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydEFzc3Vy
# ZWRJRENBLTEuY3J0MA0GCSqGSIb3DQEBBQUAA4IBAQCdJX4bM02yJoFcm4bOIyAP
# gIfliP//sdRqLDHtOhcZcRfNqRu8WhY5AJ3jbITkWkD73gYBjDf6m7GdJH7+IKRX
# rVu3mrBgJuppVyFdNC8fcbCDlBkFazWQEKB7l8f2P+fiEUGmvWLZ8Cc9OB0obzpS
# CfDscGLTYkuw4HOmksDTjjHYL+NtFxMG7uQDthSr849Dp3GdId0UyhVdkkHa+Q+B
# 0Zl0DSbEDn8btfWg8cZ3BigV6diT5VUW8LsKqxzbXEgnZsijiwoc5ZXarsQuWaBh
# 3drzbaJh6YoLbewSGL33VVRAA5Ira8JRwgpIr7DUbuD0FAo6G+OPPcqvao173NhE
# MIIGzTCCBbWgAwIBAgIQBv35A5YDreoACus/J7u6GzANBgkqhkiG9w0BAQUFADBl
# MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
# d3cuZGlnaWNlcnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJv
# b3QgQ0EwHhcNMDYxMTEwMDAwMDAwWhcNMjExMTEwMDAwMDAwWjBiMQswCQYDVQQG
# EwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNl
# cnQuY29tMSEwHwYDVQQDExhEaWdpQ2VydCBBc3N1cmVkIElEIENBLTEwggEiMA0G
# CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDogi2Z+crCQpWlgHNAcNKeVlRcqcTS
# QQaPyTP8TUWRXIGf7Syc+BZZ3561JBXCmLm0d0ncicQK2q/LXmvtrbBxMevPOkAM
# Rk2T7It6NggDqww0/hhJgv7HxzFIgHweog+SDlDJxofrNj/YMMP/pvf7os1vcyP+
# rFYFkPAyIRaJxnCI+QWXfaPHQ90C6Ds97bFBo+0/vtuVSMTuHrPyvAwrmdDGXRJC
# geGDboJzPyZLFJCuWWYKxI2+0s4Grq2Eb0iEm09AufFM8q+Y+/bOQF1c9qjxL6/s
# iSLyaxhlscFzrdfx2M8eCnRcQrhofrfVdwonVnwPYqQ/MhRglf0HBKIJAgMBAAGj
# ggN6MIIDdjAOBgNVHQ8BAf8EBAMCAYYwOwYDVR0lBDQwMgYIKwYBBQUHAwEGCCsG
# AQUFBwMCBggrBgEFBQcDAwYIKwYBBQUHAwQGCCsGAQUFBwMIMIIB0gYDVR0gBIIB
# yTCCAcUwggG0BgpghkgBhv1sAAEEMIIBpDA6BggrBgEFBQcCARYuaHR0cDovL3d3
# dy5kaWdpY2VydC5jb20vc3NsLWNwcy1yZXBvc2l0b3J5Lmh0bTCCAWQGCCsGAQUF
# BwICMIIBVh6CAVIAQQBuAHkAIAB1AHMAZQAgAG8AZgAgAHQAaABpAHMAIABDAGUA
# cgB0AGkAZgBpAGMAYQB0AGUAIABjAG8AbgBzAHQAaQB0AHUAdABlAHMAIABhAGMA
# YwBlAHAAdABhAG4AYwBlACAAbwBmACAAdABoAGUAIABEAGkAZwBpAEMAZQByAHQA
# IABDAFAALwBDAFAAUwAgAGEAbgBkACAAdABoAGUAIABSAGUAbAB5AGkAbgBnACAA
# UABhAHIAdAB5ACAAQQBnAHIAZQBlAG0AZQBuAHQAIAB3AGgAaQBjAGgAIABsAGkA
# bQBpAHQAIABsAGkAYQBiAGkAbABpAHQAeQAgAGEAbgBkACAAYQByAGUAIABpAG4A
# YwBvAHIAcABvAHIAYQB0AGUAZAAgAGgAZQByAGUAaQBuACAAYgB5ACAAcgBlAGYA
# ZQByAGUAbgBjAGUALjALBglghkgBhv1sAxUwEgYDVR0TAQH/BAgwBgEB/wIBADB5
# BggrBgEFBQcBAQRtMGswJAYIKwYBBQUHMAGGGGh0dHA6Ly9vY3NwLmRpZ2ljZXJ0
# LmNvbTBDBggrBgEFBQcwAoY3aHR0cDovL2NhY2VydHMuZGlnaWNlcnQuY29tL0Rp
# Z2lDZXJ0QXNzdXJlZElEUm9vdENBLmNydDCBgQYDVR0fBHoweDA6oDigNoY0aHR0
# cDovL2NybDMuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0QXNzdXJlZElEUm9vdENBLmNy
# bDA6oDigNoY0aHR0cDovL2NybDQuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0QXNzdXJl
# ZElEUm9vdENBLmNybDAdBgNVHQ4EFgQUFQASKxOYspkH7R7for5XDStnAs0wHwYD
# VR0jBBgwFoAUReuir/SSy4IxLVGLp6chnfNtyA8wDQYJKoZIhvcNAQEFBQADggEB
# AEZQPsm3KCSnOB22WymvUs9S6TFHq1Zce9UNC0Gz7+x1H3Q48rJcYaKclcNQ5IK5
# I9G6OoZyrTh4rHVdFxc0ckeFlFbR67s2hHfMJKXzBBlVqefj56tizfuLLZDCwNK1
# lL1eT7EF0g49GqkUW6aGMWKoqDPkmzmnxPXOHXh2lCVz5Cqrz5x2S+1fwksW5Etw
# TACJHvzFebxMElf+X+EevAJdqP77BzhPDcZdkbkPZ0XN1oPt55INjbFpjE/7WeAj
# D9KqrgB87pxCDs+R1ye3Fu4Pw718CqDuLAhVhSK46xgaTfwqIa1JMYNHlXdx3LEb
# S0scEJx3FMGdTy9alQgpECYxggQ/MIIEOwIBATB6MGwxCzAJBgNVBAYTAkNaMRcw
# FQYDVQQIEw5DemVjaCBSZXB1YmxpYzENMAsGA1UEBxMEQnJubzEQMA4GA1UEChMH
# U2V2ZWNlazEjMCEGA1UEAxMaU2V2ZWNlayBFbnRlcnByaXNlIFJvb3QgQ0ECCioc
# hHAAAQAAAH8wDQYJYIZIAWUDBAIBBQCggYQwGAYKKwYBBAGCNwIBDDEKMAigAoAA
# oQKAADAZBgkqhkiG9w0BCQMxDAYKKwYBBAGCNwIBBDAcBgorBgEEAYI3AgELMQ4w
# DAYKKwYBBAGCNwIBFTAvBgkqhkiG9w0BCQQxIgQgStm0DNIxxtOY7YOsDaN23WYj
# hHbIzdlgEsNhg6Gc97cwDQYJKoZIhvcNAQEBBQAEggEAfLvUywt25tOzi8834ze3
# ZlXwciNZVWv32ik9X6qPnLb61fEnhNkgj+M1MgwUy9kwPGfdEA+JumnilojhJwlW
# 4TbNog/NVRfJ0bqTONFC3SLxFSh60w3RyII++CSdG0w0Etu44G4k/jeSdZfmmKMx
# YadNPoNU+Qy9JxBXKyQiSBOydh6+qhIGhyMRjtPlHXL4malqVroWkYwXnz3DuaVW
# GSIB3LIkF/6bJENtEbb8Ewh2nshfF0wgxybeck1iEXUvjyoIa2yk/jlJ3T144fIy
# Wbzfk8EsEU1muCL1nWs4UrAvsxZbX9w0TLDKdMBoyvxjLPX2HvVpwMFZjOIf0ws8
# aqGCAg8wggILBgkqhkiG9w0BCQYxggH8MIIB+AIBATB2MGIxCzAJBgNVBAYTAlVT
# MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5j
# b20xITAfBgNVBAMTGERpZ2lDZXJ0IEFzc3VyZWQgSUQgQ0EtMQIQAwGaAjr/WLFr
# 1tXq5hfwZjAJBgUrDgMCGgUAoF0wGAYJKoZIhvcNAQkDMQsGCSqGSIb3DQEHATAc
# BgkqhkiG9w0BCQUxDxcNMTkwOTIyMDUxODA5WjAjBgkqhkiG9w0BCQQxFgQUISQr
# uYiCpxKe3i/3IYX4n6yeARgwDQYJKoZIhvcNAQEBBQAEggEAbqRATZi2khr2dgON
# jTZNp0UvZoGL8F1QFEqrkGw8ZxplZHygxzum0tBPg5Gd3EwFLKJhu603NkxNSdX8
# WvQdtz9kY1sVNWiRKdfIO7+GPI2w34+TkYI1JE1xLeiUAZu88/BV5P1zSgOkBbg8
# U3ptU39XZrDN6CYU9WGVgEZxCWiVNRFquMSad8gqE7HnBphjVZQtu78B7Bqegdly
# fXbqOnEiL3hNPEcICJ8poQI3zyOraDaRQ9dGIvN3ymvd6aiD1ORdFhhfalLT2g2s
# PmZ0y5VX73rF2wIYkMLu+g4mdP1XYbpNIpaKKZ4qsBYMqMNdhoqBWz1mjjXRdImE
# Y/IgKA==
# SIG # End signature block