Microsoft.AVS.VMFS/Microsoft.AVS.VMFS.psm1 (1,642 lines of code) (raw):
<#
.SYNOPSIS
This function updates all hosts in the specified cluster to have the following iSCSI configurations:
1. SCSI IP address are added as dynamic iSCSI addresses.
2. iSCSI Software Adapter is enabled.
3. Apply iSCSI best practices configuration on dynamic targets.
.PARAMETER ClusterName
Cluster name
.PARAMETER ScsiIpAddress
IP Address to add as dynamic iSCSI target
.PARAMETER LoginTimeout
Optional. Login timeout in seconds (default 30)
.PARAMETER NoopOutTimeout
Optional. NoopOut timeout in seconds (default 30)
.PARAMETER RecoveryTimeout
Optional. Recovery timeout in seconds (default 45)
.EXAMPLE
Set-VmfsIscsi -ClusterName "myCluster" -ScsiIpAddress "192.168.0.1"
.INPUTS
vCenter cluster name, Primary SCSI IP Addresses.
.OUTPUTS
None.
#>
function Set-VmfsIscsi {
[CmdletBinding()]
[AVSAttribute(10, UpdatesSDDC = $false, AutomationOnly = $true)]
Param (
[Parameter(
Mandatory=$true,
HelpMessage = 'Cluster name in vCenter')]
[ValidateNotNull()]
[String]
$ClusterName,
[Parameter(
Mandatory=$true,
HelpMessage = 'Primary IP Address to add as dynamic iSCSI target')]
[ValidateNotNull()]
[String]
$ScsiIpAddress,
[Parameter (
Mandatory = $false,
HelpMessage = 'Login timeout in seconds'
)]
[ValidateRange(1, 60)]
[int] $LoginTimeout = 30,
[Parameter (
Mandatory = $false,
HelpMessage = 'NoopOut timeout in seconds'
)]
[ValidateRange(10, 30)]
[int] $NoopOutTimeout = 30,
[Parameter (
Mandatory = $false,
HelpMessage = 'Recovery timeout in seconds'
)]
[ValidateRange(1, 120)]
[int] $RecoveryTimeout = 45
)
try {
[ipaddress] $ScsiIpAddress
}
catch {
throw "Invalid SCSI IP address $ScsiIpAddress provided."
}
$Cluster = Get-Cluster -Name $ClusterName -ErrorAction Ignore
if (-not $Cluster) {
throw "Cluster $ClusterName does not exist."
}
$VMHosts = $Cluster | Get-VMHost
foreach ($VMHost in $VMHosts) {
$Iscsi = $VMHost | Get-VMHostStorage
if ($Iscsi.SoftwareIScsiEnabled -ne $true) {
$VMHost | Get-VMHostStorage | Set-VMHostStorage -SoftwareIScsiEnabled $True | Out-Null
}
$IscsiAdapter = $VMHost | Get-VMHostHba -Type iScsi | Where-Object {$_.Model -eq "iSCSI Software Adapter"}
if (!(Get-IScsiHbaTarget -IScsiHba $IscsiAdapter -Type Send -ErrorAction stop | Where-Object {$_.Address -cmatch $ScsiIpAddress})) {
New-IScsiHbaTarget -IScsiHba $IscsiAdapter -Address $ScsiIpAddress -ErrorAction stop
}
$EsxCli = $VMHost | Get-EsxCli -v2
$IscsiArgs = $EsxCli.iscsi.adapter.discovery.sendtarget.param.get.CreateArgs()
$IscsiArgs.adapter = $IscsiAdapter.Device
$IscsiArgs.address = $ScsiIpAddress
function Set-IscsiConfig($Name, $Value) {
$CurrentValue = $EsxCli.iscsi.adapter.discovery.sendtarget.param.get.invoke($IscsiArgs) | Where-Object {$_.name -eq $Name}
if ($CurrentValue.Current -ne $Value) {
$IscsiArgs = $EsxCli.iscsi.adapter.discovery.sendtarget.param.set.CreateArgs()
$IscsiArgs.adapter = $IscsiAdapter.Device
$IscsiArgs.address = $ScsiIpAddress
$IscsiArgs.value = $Value
$IscsiArgs.key = $Name
$EsxCli.iscsi.adapter.discovery.sendtarget.param.set.invoke($IscsiArgs) | Out-Null
}
}
Set-IscsiConfig -Name "DelayedAck" -Value "false"
Set-IscsiConfig -Name "LoginTimeout" -Value $LoginTimeout
Set-IscsiConfig -Name "NoopOutTimeout" -Value $NoopOutTimeout
Set-IscsiConfig -Name "RecoveryTimeout" -Value $RecoveryTimeout
}
Write-Host "Successfully configured VMFS iSCSI for cluster $ClusterName."
}
<#
.SYNOPSIS
This function updates all hosts in the specified cluster to have the following iSCSI configurations:
1. SCSI IP address are added as static iSCSI addresses.
2. iSCSI Software Adapter is enabled.
3. Apply iSCSI best practices configuration on static targets.
.PARAMETER ClusterName
Cluster name
.PARAMETER ScsiIpAddress
IP Address to add as static iSCSI target
.PARAMETER ScsiName
iSCSI target name
.PARAMETER LoginTimeout
Optional. Login timeout in seconds (default 30)
.PARAMETER NoopOutTimeout
Optional. NoopOut timeout in seconds (default 30)
.PARAMETER RecoveryTimeout
Optional. Recovery timeout in seconds (default 45)
.EXAMPLE
Set-VmfsIscsi -ClusterName "myCluster" -ScsiIpAddress "192.168.0.1" -IscsitName "iqn.1998-01.com.vmware:target-1"
.INPUTS
vCenter cluster name, Primary SCSI IP Addresses. iSCSI target name
.OUTPUTS
None.
#>
function Set-VmfsStaticIscsi {
[CmdletBinding()]
[AVSAttribute(10, UpdatesSDDC = $false, AutomationOnly = $true)]
Param (
[Parameter(
Mandatory = $true,
HelpMessage = 'Cluster name in vCenter')]
[ValidateNotNull()]
[String]
$ClusterName,
[Parameter(
Mandatory = $true,
HelpMessage = 'Primary IP Address to add as static iSCSI target')]
[ValidateNotNull()]
[String]
$ScsiIpAddress,
[Parameter(
Mandatory = $true,
HelpMessage = 'iSCSI target name')]
[String] $ScsiName,
[Parameter (
Mandatory = $false,
HelpMessage = 'Login timeout in seconds'
)]
[ValidateRange(1, 60)]
[int] $LoginTimeout = 30,
[Parameter (
Mandatory = $false,
HelpMessage = 'NoopOut timeout in seconds'
)]
[ValidateRange(10, 30)]
[int] $NoopOutTimeout = 30,
[Parameter (
Mandatory = $false,
HelpMessage = 'Recovery timeout in seconds'
)]
[ValidateRange(1, 120)]
[int] $RecoveryTimeout = 45
)
try {
[ipaddress] $ScsiIpAddress
}
catch {
throw "Invalid SCSI IP address $ScsiIpAddress provided."
}
$Cluster = Get-Cluster -Name $ClusterName -ErrorAction Ignore
if (-not $Cluster) {
throw "Cluster $ClusterName does not exist."
}
$VMHosts = $Cluster | Get-VMHost
foreach ($VMHost in $VMHosts) {
$Iscsi = $VMHost | Get-VMHostStorage
if ($Iscsi.SoftwareIScsiEnabled -ne $true) {
$VMHost | Get-VMHostStorage | Set-VMHostStorage -SoftwareIScsiEnabled $True | Out-Null
}
$IscsiAdapter = $VMHost | Get-VMHostHba -Type iScsi | Where-Object { $_.Model -eq "iSCSI Software Adapter" }
if (!(Get-IScsiHbaTarget -IScsiHba $IscsiAdapter -Type "Static" -ErrorAction stop | Where-Object { $_.Address -cmatch $ScsiIpAddress })) {
New-IScsiHbaTarget -IScsiHba $IscsiAdapter -Type "Static" -Address $ScsiIpAddress -IScsiName $ScsiName -ErrorAction stop
Write-Verbose "Added static iSCSI target $ScsiName with address $ScsiIpAddress to $VMHost"
}
$EsxCli = $VMHost | Get-EsxCli -v2
function Set-StaticIscsiConfig($Name, $Value) {
$IscsiArgs = $EsxCli.iscsi.adapter.target.portal.param.get.CreateArgs()
$IscsiArgs.adapter = $IscsiAdapter.Device
$IscsiArgs.address = $ScsiIpAddress
$IscsiArgs.name = $ScsiName
$CurrentValue = $EsxCli.iscsi.adapter.target.portal.param.get.invoke($IscsiArgs) | Where-Object { $_.name -eq $Name }
if ($CurrentValue.Current -ne $Value) {
$IscsiArgs = $EsxCli.iscsi.adapter.target.portal.param.set.CreateArgs()
$IscsiArgs.adapter = $IscsiAdapter.Device
$IscsiArgs.address = $ScsiIpAddress
$IscsiArgs.name = $ScsiName
$IscsiArgs.inherit = $false
$IscsiArgs.value = $Value
$IscsiArgs.key = $Name
$EsxCli.iscsi.adapter.target.portal.param.set.invoke($IscsiArgs) | Out-Null
Write-verbose "Set $Name to $Value for $ScsiName"
}
}
Set-StaticIscsiConfig -Name "DelayedAck" -Value "false"
Set-StaticIscsiConfig -Name "LoginTimeout" -Value $LoginTimeout
Set-StaticIscsiConfig -Name "NoopOutTimeout" -Value $NoopOutTimeout
Set-StaticIscsiConfig -Name "RecoveryTimeout" -Value $RecoveryTimeout
}
Write-Host "Successfully configured VMFS iSCSI for cluster $ClusterName."
}
<#
.SYNOPSIS
Creates a new VMFS datastore and mounts to a VMware cluster.
.PARAMETER ClusterName
Cluster name
.PARAMETER DatastoreName
Datastore name
.PARAMETER DeviceNaaId
NAA ID of device used to create a new VMFS datastore
.PARAMETER Size
Datastore capacity size in bytes
.EXAMPLE
New-VmfsDatastore -ClusterName "myCluster" -DatastoreName "myDatastore" -DeviceNaaId $DeviceNaaId -Size <size-in-bytes>
.INPUTS
vCenter cluster name, datastore name, device NAA ID and datastore size.
.OUTPUTS
None.
#>
function New-VmfsDatastore {
[CmdletBinding()]
[AVSAttribute(10, UpdatesSDDC = $false, AutomationOnly = $true)]
Param (
[Parameter(
Mandatory=$true,
HelpMessage = 'Cluster name in vCenter')]
[ValidateNotNull()]
[String]
$ClusterName,
[Parameter(
Mandatory=$true,
HelpMessage = 'Name of VMFS datastore to be created in vCenter')]
[ValidateNotNull()]
[String]
$DatastoreName,
[Parameter(
Mandatory=$true,
HelpMessage = 'NAA ID of device used to create a new VMFS datastore')]
[ValidateNotNull()]
[String]
$DeviceNaaId,
[Parameter(
Mandatory=$true,
HelpMessage = 'Capacity of new datastore in bytes')]
[ValidateNotNull()]
[String]
$Size
)
try {
$SizeInBytes = [UInt64] $Size
} catch {
throw "Invalid Size $Size provided."
}
if (($SizeInBytes -lt 1GB) -or ($SizeInBytes -gt 64TB)) {
throw "Invalid Size $SizeInBytes provided. Size should be between 1 GB and 64 TB."
}
$Cluster = Get-Cluster -Name $ClusterName -ErrorAction Ignore
if (-not $Cluster) {
throw "Cluster $ClusterName does not exist."
}
$Datastore = Get-Datastore -Name $DatastoreName -ErrorAction Ignore
if ($Datastore) {
throw "Unable to create a datastore. Datastore '$DatastoreName' already exists."
}
# Create a new VMFS datastore with the specified size and rescan storage
try {
Write-Host "Creating datastore $DatastoreName..."
$TotalSectors = $SizeInBytes / 512
$Esxi = $Cluster | Get-VMHost | Where-Object { ($_.ConnectionState -eq 'Connected') } | Select-Object -last 1
$EsxiView = Get-View -ViewType HostSystem -Filter @{"Name" = $Esxi.name}
$DatastoreSystem = Get-View -Id $EsxiView.ConfigManager.DatastoreSystem
$Device = $DatastoreSystem.QueryAvailableDisksForVmfs($null) | Where-Object { ($_.CanonicalName -eq $DeviceNaaId) }
$DatastoreCreateOptions = $DatastoreSystem.QueryVmfsDatastoreCreateOptions($Device.DevicePath, $null)
$VmfsDatastoreCreateSpec = New-Object VMware.Vim.VmfsDatastoreCreateSpec
$VmfsDatastoreCreateSpec.DiskUuid = $Device.Uuid
$VmfsDatastoreCreateSpec.Partition = $DatastoreCreateOptions[0].Spec.Partition
$VmfsDatastoreCreateSpec.Partition.Partition[0].EndSector = $VmfsDatastoreCreateSpec.Partition.Partition[0].StartSector + $TotalSectors
$VmfsDatastoreCreateSpec.Partition.TotalSectors = $TotalSectors
$VmfsDatastoreCreateSpec.Vmfs = New-Object VMware.Vim.HostVmfsSpec
$VmfsDatastoreCreateSpec.Vmfs.VolumeName = $DatastoreName
$HostScsiDiskPartition = New-Object VMware.Vim.HostScsiDiskPartition
$HostScsiDiskPartition.DiskName = $DeviceNaaId
$HostScsiDiskPartition.Partition = $DatastoreCreateOptions[0].Info.Layout.Partition[0].Partition
$VmfsDatastoreCreateSpec.Vmfs.Extent = $HostScsiDiskPartition
$VmfsDatastoreCreateSpec.vmfs.MajorVersion = $DatastoreCreateOptions[0].Spec.Vmfs.MajorVersion
$DatastoreSystem.CreateVmfsDatastore($VmfsDatastoreCreateSpec)
} catch {
Write-Error $Global:Error[0]
}
$Cluster | Get-VMHost | Get-VMHostStorage -RescanAllHba -RescanVmfs | Out-Null
$Datastore = Get-Datastore -Name $DatastoreName -ErrorAction Ignore
if (-not $Datastore -or $Datastore.type -ne "VMFS") {
throw "Failed to create datastore $DatastoreName."
}
}
<#
.DESCRIPTION
Detach and unmount a VMFS datastore from a cluster.
.PARAMETER ClusterName
Cluster name
.PARAMETER DatastoreName
Datastore name
.EXAMPLE
Dismount-VmfsDatastore -ClusterName "myCluster" -DatastoreName "myDatastore"
.INPUTS
vCenter cluster name and datastore name.
.OUTPUTS
None.
#>
function Dismount-VmfsDatastore {
[CmdletBinding()]
[AVSAttribute(10, UpdatesSDDC = $false, AutomationOnly = $true)]
Param (
[Parameter(
Mandatory=$true,
HelpMessage = 'Cluster name in vCenter')]
[ValidateNotNull()]
[String]
$ClusterName,
[Parameter(
Mandatory=$true,
HelpMessage = 'Name of VMFS datastore to be unmounted in vCenter')]
[ValidateNotNull()]
[String]
$DatastoreName
)
$Cluster = Get-Cluster -Name $ClusterName -ErrorAction Ignore
if (-not $Cluster) {
throw "Cluster $ClusterName does not exist."
}
$Datastore = Get-Datastore -Name $DatastoreName -ErrorAction Ignore
if (-not $Datastore) {
throw "Datastore $DatastoreName does not exist."
}
if ("VMFS" -ne $Datastore.Type) {
throw "Datastore $DatastoreName is of type $($Datastore.Type). This cmdlet can only process VMFS datastores."
}
Write-Host "Unmounting datastore $DatastoreName from all hosts, detaching SCSI devices, NVMe/TCP devices are not detached."
$VMHosts = $Cluster | Get-VMHost
foreach ($VMHost in $VMHosts) {
$IsDatastoreConnectedToHost = Get-Datastore -VMHost $VMHost | Where-Object {$_.name -eq $DatastoreName}
if ($null -ne $IsDatastoreConnectedToHost) {
$VMs = $Datastore | Get-VM
if ($VMs -and $VMs.Count -gt 0) {
$vmNames = $VMs | Join-String -SingleQuote -Property {$_.Name} -Separator ", "
throw "Cannot unmount datastore $DatastoreName. It is already in use by $vmNames."
}
$Datastore = Get-Datastore -Name $DatastoreName
$VmfsUuid = $Datastore.ExtensionData.info.Vmfs.uuid
$ScsiLunUuid = ($Datastore | Get-ScsiLun).ExtensionData.uuid | Select-Object -last 1
$HostStorageSystem = Get-View $VMHost.Extensiondata.ConfigManager.StorageSystem
$HostStorageSystem.UnmountVmfsVolume($VmfsUuid) | Out-Null
Write-Host "Datastore unmounted."
$HostViewDiskName = $Datastore.ExtensionData.Info.vmfs.extent[0].Diskname;
if(($null -ne $HostViewDiskName) -and ($HostViewDiskName.StartsWith("eui."))){
Write-Host "Device UUID $($VmfsUuid) is an NVMe/TCP volume, not required to be detached, and can be mounted back to host as needed."
}
else {
$HostStorageSystem.DetachScsiLun($ScsiLunUuid) | Out-Null
}
Write-Host "Rescanning now.."
$VMHost | Get-VMHostStorage -RescanAllHba -RescanVmfs | Out-Null
}
}
}
<#
.DESCRIPTION
Expand existing VMFS volume to new size.
.PARAMETER ClusterName
Cluster name
.PARAMETER DeviceNaaId
NAA ID of device associated with the existing VMFS volume (optional). If not provided, the DatastoreName value must be provided instead.
.PARAMETER DatastoreName
Datastore name (optional). If not provided, the DeviceNaaId value must be provided instead.
.EXAMPLE
Resize-VmfsVolume -ClusterName "myClusterName" -DeviceNaaId $DeviceNaaId
.INPUTS
vCenter cluster name and device NAA ID.
.OUTPUTS
None.
#>
function Resize-VmfsVolume {
[CmdletBinding()]
[AVSAttribute(10, UpdatesSDDC = $false, AutomationOnly = $true)]
Param (
[Parameter(
Mandatory=$true,
HelpMessage = 'Cluster name in vCenter')]
[ValidateNotNull()]
[String]
$ClusterName,
[Parameter(
Mandatory=$false,
HelpMessage = 'NAA ID of device associated with the existing VMFS volume')]
[String]
$DeviceNaaId,
[Parameter(
Mandatory = $false,
HelpMessage = 'Existing datastore name')]
[String]
$DatastoreName
)
if ((-not $DeviceNaaId) -and (-not $DatastoreName)) {
throw "One of DeviceNaaId or DatastoreName values must be provided."
}
if ($DeviceNaaId -and $DatastoreName) {
throw "Cannot provide values for both DeviceNaaId and DatastoreName."
}
$Cluster = Get-Cluster -Name $ClusterName -ErrorAction Ignore
if (-not $Cluster) {
throw "Cluster $ClusterName does not exist."
}
if ($DatastoreName) {
$Datastore = Get-Datastore -Name $DatastoreName -ErrorAction Ignore
if (-not $Datastore) {
throw "Datastore $DatastoreName does not exist."
}
if ($Datastore.Type -ne "VMFS") {
throw "Datastore $DatastoreName is of type $($Datastore.Type). This cmdlet can only process iSCSI datastores."
}
$DatastoreToResize = $Datastore
} else {
$Esxi = $Cluster | Get-VMHost | Where-Object { ($_.ConnectionState -eq 'Connected') } | Select-Object -last 1
$Cluster | Get-VMHost | Get-VMHostStorage -RescanAllHba | Out-Null
$Datastores = $Esxi | Get-Datastore -ErrorAction stop
foreach ($Datastore in $Datastores) {
$CurrentNaaId = $Datastore.ExtensionData.Info.Vmfs.Extent.DiskName
if ($CurrentNaaId -eq $DeviceNaaId) {
$DatastoreToResize = $Datastore
break
}
}
}
if (-not $DatastoreToResize) {
throw "Failed to re-size VMFS volume."
}
foreach ($DatastoreHost in $DatastoreToResize.ExtensionData.Host.Key) {
Get-VMHost -id "HostSystem-$($DatastoreHost.value)" | Get-VMHostStorage -RescanAllHba -RescanVmfs -ErrorAction Stop -WarningAction SilentlyContinue | Out-Null
}
$Esxi = Get-View -Id ($DatastoreToResize.ExtensionData.Host | Select-Object -last 1 | Select-Object -ExpandProperty Key)
$DatastoreSystem = Get-View -Id $Esxi.ConfigManager.DatastoreSystem
$ExpandOptions = $DatastoreSystem.QueryVmfsDatastoreExpandOptions($DatastoreToResize.ExtensionData.MoRef)
Write-Host "Increasing the size of the VMFS volume..."
$DatastoreSystem.ExpandVmfsDatastore($DatastoreToResize.ExtensionData.MoRef, $ExpandOptions[0].spec)
}
<#
.DESCRIPTION
Re-signature existing VMFS volume to recover to previous version.
.PARAMETER ClusterName
Cluster name
.PARAMETER DeviceNaaId
NAA ID of device associated with the existing VMFS volume
.PARAMETER DatastoreName
Datastore name (optional). If not provided, an automatically generated name will be used.
.EXAMPLE
Restore-VmfsVolume -ClusterName "myClusterName" -DeviceNaaId $DeviceNaaId
.INPUTS
vCenter cluster name and device NAA ID.
.OUTPUTS
None.
#>
function Restore-VmfsVolume {
[CmdletBinding()]
[AVSAttribute(10, UpdatesSDDC = $false, AutomationOnly = $true)]
Param (
[Parameter(
Mandatory=$true,
HelpMessage = 'Cluster name in vCenter')]
[ValidateNotNull()]
[String]
$ClusterName,
[Parameter(
Mandatory=$true,
HelpMessage = 'NAA ID of device associated with the existing VMFS volume')]
[ValidateNotNull()]
[String]
$DeviceNaaId,
[Parameter(
Mandatory = $false,
HelpMessage = 'New datastore name')]
[String]
$DatastoreName
)
if (!($DeviceNaaId -like 'naa.624a9370*' -or $DeviceNaaId -like 'eui.*')) {
throw "Invalid Device NAA ID $DeviceNaaId provided."
}
$Cluster = Get-Cluster -Name $ClusterName -ErrorAction Ignore
if (-not $Cluster) {
throw "Cluster $ClusterName does not exist."
}
if ($DatastoreName) {
$Datastore = Get-Datastore -Name $DatastoreName -ErrorAction Ignore
if ($Datastore) {
throw "Datastore '$Datastore' already exists."
}
}
$Esxi = $Cluster | Get-VMHost | Where-Object { ($_.ConnectionState -eq 'Connected') } | Select-Object -last 1
$Cluster | Get-VMHost | Get-VMHostStorage -RescanAllHba | Out-Null
$HostStorageSystem = Get-View -ID $Esxi.ExtensionData.ConfigManager.StorageSystem
$ResigVolumes = $HostStorageSystem.QueryUnresolvedVmfsVolume()
foreach ($ResigVolume in $ResigVolumes) {
foreach ($ResigExtent in $ResigVolume.Extent) {
if ($ResigExtent.Device.DiskName -eq $DeviceNaaId) {
if ($ResigVolume.ResolveStatus.Resolvable -eq $false) {
if ($ResigVolume.ResolveStatus.MultipleCopies -eq $true) {
Write-Error "The volume cannot be re-signatured as more than one non re-signatured copy is present."
Write-Error "The following volume(s) need to be removed/re-signatured first:"
$ResigVolume.Extent.Device.DiskName | Where-Object {$_ -ne $DeviceNaaId}
}
throw "Failed to re-signature VMFS volume."
} else {
$VolumeToResignature = $ResigVolume
break
}
}
}
}
if ($null -eq $VolumeToResignature) {
Write-Error "No unresolved volume found on the created volume."
throw "Failed to re-signature VMFS volume."
}
Write-Host "Starting re-signature for VMFS volume..."
$EsxCli = Get-EsxCli -VMHost $Esxi -v2 -ErrorAction stop
$ResigOp = $EsxCli.storage.vmfs.snapshot.resignature.createargs()
$ResigOp.volumelabel = $VolumeToResignature.VmfsLabel
$EsxCli.storage.vmfs.snapshot.resignature.invoke($ResigOp) | Out-Null
Start-Sleep -s 5
# If a new datastore name is specified by the user
if (-not [string]::IsNullOrEmpty($DatastoreName)) {
$Cluster | Get-VMHost | Get-VMHostStorage -RescanAllHba -RescanVMFS | Out-Null
$ds = $Esxi | Get-Datastore -ErrorAction stop | Where-Object { $_.ExtensionData.Info.Vmfs.Extent.DiskName -eq $DeviceNaaId }
# Snapshot datastore will always start with "snap*""
if (-not $ds.Name -like "snap*") {
throw "Can't rename datastore $($ds.Name), the datastore is not restored from snapshot..."
}
Write-Host "Renaming $($ds.Name) to $DatastoreName...."
$ds | Set-Datastore -Name $DatastoreName -ErrorAction stop | Out-Null
}
$Cluster | Get-VMHost | Get-VMHostStorage -RescanAllHba -RescanVMFS | Out-Null
}
<#
.SYNOPSIS
Rescans host storage
.PARAMETER VMHostName
Name of the VMHost (ESXi server)
.EXAMPLE
Sync-VMHostStorage -VMHostName "vmhost1"
.INPUTS
VMHostName.
.OUTPUTS
None.
#>
function Sync-VMHostStorage {
[CmdletBinding()]
[AVSAttribute(10, UpdatesSDDC = $false)]
Param (
[Parameter(
Mandatory=$true,
HelpMessage = 'VMHost name')]
[ValidateNotNull()]
[String]
$VMHostName
)
Get-VMHost $VMHostName | Get-VMHostStorage -RescanAllHba -RescanVMFS | Out-Null
}
<#
.SYNOPSIS
Rescans all host storage in cluster
.PARAMETER ClusterName
Cluster name
.EXAMPLE
Sync-ClusterVMHostStorage -ClusterName "myClusterName"
.INPUTS
vCenter cluster name
.OUTPUTS
None
#>
function Sync-ClusterVMHostStorage {
[CmdletBinding()]
[AVSAttribute(10, UpdatesSDDC = $false)]
Param (
[Parameter(
Mandatory=$true,
HelpMessage = 'Cluster name in vCenter')]
[ValidateNotNull()]
[String]
$ClusterName
)
$Cluster = Get-Cluster -Name $ClusterName -ErrorAction Ignore
if (-not $Cluster) {
throw "Cluster $ClusterName does not exist."
}
$Cluster | Get-VMHost | Get-VMHostStorage -RescanAllHba -RescanVMFS | Out-Null
}
<#
.SYNOPSIS
This function removes the specified static iSCSI configurations from all of Esxi Hosts in a cluster
.PARAMETER ClusterName
Cluster name
.PARAMETER iSCSIAddress
iSCSI target address. Multiple addresses can be seperated by ","
.PARAMETER VMHostName
Name of the VMHost (ESXi server). If not specified, all hosts in the cluster will be updated.
.EXAMPLE
Remove-VMHostStaticIScsiTargets -ClusterName "myCluster" -ISCSIAddress "192.168.1.10,192.168.1.11"
.INPUTS
vCenter cluster name and iSCSi target address
.OUTPUTS
None
#>
function Remove-VMHostStaticIScsiTargets {
[CmdletBinding()]
[AVSAttribute(10, UpdatesSDDC = $false, AutomationOnly = $true)]
Param (
[Parameter(
Mandatory=$true,
HelpMessage = 'Cluster name in vCenter')]
[ValidateNotNull()]
[String]
$ClusterName,
[Parameter(
Mandatory=$false,
HelpMessage = 'VMHost name')]
[String]
$VMHostName,
[Parameter(
Mandatory=$true,
HelpMessage = 'IP Address of static iSCSI target to remove. Multiple addresses can be seperated by ","')]
[ValidateNotNull()]
[String]
$iSCSIAddress
)
$Cluster = Get-Cluster -Name $ClusterName -ErrorAction Ignore
if (-not $Cluster) {
throw "Cluster $ClusterName does not exist."
}
$iSCSIAddressList = $iSCSIAddress.Split(",")
$DatastoreDisks = Get-Datastore | Select-Object -ExpandProperty ExtensionData | Select-Object -ExpandProperty Info | Select-Object -ExpandProperty Vmfs | Select-Object -ExpandProperty Extent
$TargetsChanged = $False
$VMHosts = $null
if ($VMHostName) {
$VMHosts = $Cluster| Get-VMHost -Name $VMHostName
}
else {
$VMHosts = $Cluster | Get-VMHost
}
if (-not $VMHosts) {
throw "No hosts found in cluster $ClusterName"
}
# Remove iSCSI ip address from static discovery from all of hosts if there is a match
$HBAs = $VMHosts | Get-VMHostHba -Type iScsi
foreach ($HBA in $HBAs) {
$DeviceIds = ($HBA | Get-ScsiLun).CanonicalName
# Find if any of the devices is used as backing for a datastore
$IsDeviceInUse = $False
foreach ($DeviceId in $DeviceIds) {
if ($DatastoreDisks.DiskName -contains $DeviceId) {
$IsDeviceInUse = $True
break
}
}
if ($IsDeviceInUse) {
Write-Warning "Datastore disk $DeviceId for host $($HBA.VMHost.Name) is in use, skipping iSCSI target removal"
}
else {
$Targets = $HBA | Get-IScsiHbaTarget | Where-Object {($_.Type -eq "Static") -and ($iSCSIAddressList -contains $_.Address)}
foreach ($Target in $Targets) {
Write-Host "Removing iSCSI target $Target from host $($HBA.VMHost.Name)"
try {
$Target | Remove-IScsiHbaTarget -Confirm:$false
$TargetsChanged = $True
}
catch {
Write-Error "Failed to remove iSCSI target $Target from host $($HBA.VMHost.Name) with error: $_"
}
}
}
}
if ($TargetsChanged) {
# Rescan after removing the iSCSI targets
Write-Host "Rescanning storage"
$Cluster | Get-VMHost | Get-VMHostStorage -RescanAllHba -RescanVMFS | Out-Null
}
}
<#
.SYNOPSIS
This function connects all ESXi host(s) to the specified storage cluster node/target via NVMe/TCP.
1. vSphere Cluster Name
2. Storage Node EndPoint Network address
3. Storage SystemNQN
4. NVMe/TCP Admin Queue Size (Optional)
5. Controller Id (Optional)
6. IO Queue Number (Optional)
7. IO Queue Size (Optional)
8. Keep Alive Timeout (Optional)
9. Target Port Number (Optional)
.PARAMETER ClusterName
vSphere Cluster Name
.PARAMETER NodeAddress
Storage Node EndPoint Address
.PARAMETER StorageSystemNQN
Storage system NQN
.PARAMETER AdminQueueSize
NVMe/TCP Admin Queue Size, default 32
.PARAMETER ControllerId
NVMe/TCP Controller ID, default 65535
.PARAMETER IoQueueNumber
IO Queue Number, default 8
.PARAMETER IoQueueSize
IO Queue Size, default 256
.PARAMETER KeepAliveTimeout
Keep Alive Timeout, default 256
.PARAMETER PortNumber
Target Port Number, default 4420
.EXAMPLE
Connect-NVMeTCPTarget ClusterName "Cluster-001" -NodeAddress "192.168.0.1" -StorageSystemNQN "nqn.2016-01.com.lightbitslabs:uuid:46edb489-ba18-4dd4-a157-1d8eb8c32e21"
.INPUTS
vSphere Cluster Name, Storage Node Address, Storage System NQN
.OUTPUTS
None.
#>
function Connect-NVMeTCPTarget {
[CmdletBinding()]
[AVSAttribute(10, UpdatesSDDC = $false, AutomationOnly = $true)]
Param
(
[Parameter(
Mandatory = $true,
HelpMessage = 'vSphere Cluster Name')]
[String] $ClusterName,
[Parameter(
Mandatory = $true,
HelpMessage = 'Target storage Node datapath address')]
[string] $NodeAddress,
[Parameter(
Mandatory = $true,
HelpMessage = 'Target storage SystemNQN')]
[string] $StorageSystemNQN,
[Parameter(
Mandatory = $false,
HelpMessage = 'NVMe/TCP Admin Queue Size')]
[int] $AdminQueueSize = 32,
[Parameter(
Mandatory = $false,
HelpMessage = 'NVMe/TCP Controller Id')]
[int] $ControllerId = 65535,
[Parameter(
Mandatory = $false,
HelpMessage = 'NVMe/TCP IO Queue Number')]
[int] $IoQueueNumber = 8,
[Parameter(
Mandatory = $false,
HelpMessage = 'NVMe/TCP IO Queue Size')]
[int] $IoQueueSize = 256,
[Parameter(
Mandatory = $false,
HelpMessage = 'Keep Alive Timeout')]
[int] $KeepAliveTimeout = 256,
[Parameter(
Mandatory = $false,
HelpMessage = 'Port Number')]
[int] $PortNumber = 4420
)
Write-Host "Connecting to target via Storage Adapter from ESXi host(s) under Cluster " $ClusterName
Write-Host " " ;
$Cluster = Get-Cluster -Name $ClusterName -ErrorAction Ignore
if (-not $Cluster) {
throw "Cluster $ClusterName does not exist."
}
$VmHosts = $Cluster | Get-VMHost
foreach ($VmHost in $VMHosts) {
if ($VmHost.ConnectionState -ne "Connected") {
Write-Host "ESXi host $($VmHost.Name) must be in connected state, ignoring operation because of current state "$VmHost.ConnectionState
Write-Host ""
continue
}
$StorageAdapters = $VmHost | Get-VMHostHba
$HostEsxcli = $null;
try {
$HostEsxcli = Get-EsxCli -VMHost $VmHost.Name
}
catch {
Write-Error "Failed to execute Get-EsxCli cmdlet on host $($VmHost.Name), continue connecting rest of the host(s) "
continue
}
Write-Host "Connected to host via PowerCLI-esxcli $($VmHost.Name)"
foreach ($StorageAdapter in $StorageAdapters) {
if (($StorageAdapter.Driver -eq "nvmetcp")) {
if ($HostEsxcli) {
$Name = $StorageAdapter.Name.ToString().Trim()
Write-Host "Connecting Adapter $($Name) to storage controller"
try {
$EsxCliResult = $HostEsxcli.nvme.fabrics.connect(
$Name, $AdminQueueSize, $ControllerId,
$null, $IoQueueNumber, $IoQueueSize, $NodeAddress,
$KeepAliveTimeout, $PortNumber, $StorageSystemNQN, $null, $null
);
if ($EsxCliResult) {
Write-Host "ESXi host $($VmHost.Name) is connected to storage controller via " $Name
}
else {
Write-Host "Failed to connect ESXi host $($VmHost.Name) to storage controller "
}
Write-Host "Connecting Controller status: "$EsxCliResult;
}
catch {
Write-Error "Failed to connect ESXi NVMe/TCP storage adapter to storage controller. $($_.Exception) "
}
}
}
}
Write-Host "Rescanning NVMe/TCP storage adapter.."
$RescanResult = Get-VMHostStorage -VMHost $VmHost.Name -RescanAllHba
Write-Host "Rescanning Completed."
Write-Host ""
}
}
<#
.SYNOPSIS
This function disconnects all ESXi host(s) from the specified storage cluster node/target.
1. vSphere Cluster Name
2. Storage SystemNQN
.PARAMETER ClusterName
vSphere Cluster Name
.PARAMETER StorageSystemNQN
Storage system NQN
.EXAMPLE
Disconnect-NVMeTCPTarget -ClusterName "Cluster-001" -StorageSystemNQN "nqn.2016-01.com.lightbitslabs:uuid:46edb489-ba18-4dd4-a157-1d8eb8c32e21"
.INPUTS
vSphere Cluster Name, Storage SystemNQN
.OUTPUTS
None.
#>
function Disconnect-NVMeTCPTarget {
[CmdletBinding()]
[AVSAttribute(10, UpdatesSDDC = $false, AutomationOnly = $true)]
Param
(
[Parameter(
Mandatory = $true,
HelpMessage = 'vSphere Cluster Name')]
[String] $ClusterName,
[Parameter(
Mandatory = $true,
HelpMessage = 'Target storage SystemNQN')]
[string] $StorageSystemNQN
)
Write-Host "Disconnecting ESXi host(s) from storage target under Cluster " $ClusterName
Write-Host " " ;
$Cluster = Get-Cluster -Name $ClusterName -ErrorAction Ignore
if (-not $Cluster) {
throw "Cluster $ClusterName does not exist."
}
$VmHosts = $Cluster | Get-VMHost
foreach ($VmHost in $VMHosts) {
if ($VmHost.ConnectionState -ne "Connected") {
Write-Host "ESXi host $($VmHost.Name) must be in connected state, ignoring operation because of current state "$VmHost.ConnectionState
Write-Host ""
continue
}
$ProvisionedDevices = Get-Datastore -VMHost $VmHost.Name | where-object{$_.ExtensionData.Info.Vmfs.Extent.DiskName -like 'eui.*'}
if(($Null -ne $ProvisionedDevices) -and ($ProvisionedDevices.Length -gt 0)){
Write-Host "Storage device(s) found on host $($VmHost.Name) from target, skipping to disconnect."
Write-Host ""
continue
}
$StorageAdapters = $VmHost | Get-VMHostHba
if (!$StorageAdapters) {
Write-Host "No Storage adapter to disconnect"
continue
}
$HostEsxcli = $null;
try {
$HostEsxcli = Get-EsxCli -VMHost $VmHost.Name
}
catch {
Write-Error "Failed to execute Get-EsxCli cmdlet on host $($VmHost.Name), continue diconnecting rest of the host(s) "
continue
}
Write-Host "Connected to host via PowerCLI-esxcli $($VmHost.Name)"
if ($HostEsxcli) {
$Controllers = $HostEsxcli.nvme.controller.list();
if ($Controllers -and $Controllers.Count -ge 0) {
foreach ($item in $Controllers) {
try {
Write-Host "Diconnecting "$item.Adapter
$result = $HostEsxcli.nvme.fabrics.disconnect($item.Adapter, $item.ControllerNumber, $StorageSystemNQN);
Write-Host "Diconnecting Controller status: "$result;
}
catch {
Write-Host "Failed to disconnect controller $($_.Exception)"
}
}
Write-Host "Rescanning NVMe/TCP storage adapter.."
$RescanResult = Get-VMHostStorage -VMHost $VmHost.Name -RescanAllHba
Write-Host "Rescanning Completed."
}
else {
Write-Host "No NVMe/TCP controller found on given host " $VmHost.Name
}
}
Write-Host ""
}
}
<#
.SYNOPSIS
This function removes VMFS datastore on a given ESXi Cluster.
1. vSphere Cluster Name
2. Datastore Name
.PARAMETER HostAddress
vSphere Cluster Name
.PARAMETER DatastoreName
Datastore Name
.EXAMPLE
Remove-VmfsDatastore -ClusterName "vSphere-cluster-001" -DatastoreName "datastore-name-01"
.INPUTS
vSphere Cluster Name, Datastore name
.OUTPUTS
None.
#>
function Remove-VmfsDatastore {
[CmdletBinding()]
[AVSAttribute(10, UpdatesSDDC = $false, AutomationOnly = $true)]
Param
(
[Parameter(
Mandatory = $true,
HelpMessage = 'vSphere Cluster Name')]
[string] $ClusterName,
[Parameter(
Mandatory = $true,
HelpMessage = ' Existing datastore name')]
[string] $DatastoreName
)
Write-Host "Removing datastore $($DatastoreName) accessible to ESXi host(s) in the cluster " $ClusterName
$AvailableDatastore = $null
$ClusterName = $ClusterName.Trim()
$Cluster = Get-Cluster -Name $ClusterName -ErrorAction Ignore
if (-not $Cluster) {
throw "Cluster $ClusterName does not exist."
}
$AvailableDatastore = Get-Datastore -Name $DatastoreName -ErrorAction ignore
if ( (-not $AvailableDatastore) -or ($AvailableDatastore.State -eq "Unavailable")) {
throw "Datastore $DatastoreName does not exist Or datastore is in Unvailable state."
}
$VMs = Get-VM -Datastore $DatastoreName -ErrorAction ignore
if ($VMs){
throw "Datastore $DatastoreName is hosting worker virtual machines and can't be deleted"
}
$VmHosts = $Cluster | Get-VMHost -State "Connected"
$RelatedVmHosts = Get-VMHost -Datastore $DatastoreName -State "Connected"
$DeleteDs = $true
foreach ($RltdHost in $RelatedVmHosts){
if($RltdHost.Parent.Name.Trim() -ne $ClusterName){
$DeleteDs = $false
break;
}
}
$IsDatastoreRemoved=$false
if($DeleteDs){
try {
Write-Host "Removing datastore using esxi host $($RelatedVmHosts[0].Name) as reference host."
Remove-Datastore -VMHost $RelatedVmHosts[0].Name -Datastore $DatastoreName -Confirm:$false
$AvailableDatastore = $null
$AvailableDatastore = Get-Datastore -Name $DatastoreName -ErrorAction ignore
if (-not $AvailableDatastore) {
Write-Host "Datastore removed. "
$IsDatastoreRemoved=$true
}
}
catch {
throw "Failed to remove datasore $($DatastoreName)."
}
}
else{
Write-Host "Datastore is shared, Unmounting datastore from each host under the cluster $($ClusterName)"
$VmfsUuid = $AvailableDatastore.ExtensionData.info.Vmfs.uuid
foreach ($VmHost in $VMHosts) {
try {
$HostStorageSystem = Get-View $VmHost.Extensiondata.ConfigManager.StorageSystem
$HostStorageSystem.UnmountVmfsVolume($VmfsUuid) | Out-Null
}
catch {
Write-Host "Failed to unmount datastore from host "$VmHost.Name
}
}
}
Write-Host "Rescanning datastore "
$RescanResult = Get-VMHostStorage -VMHost $RelatedVmHosts[0].Name -RescanAllHba
if (-not($IsDatastoreRemoved)){
Write-Host "Datastore was found but did't remove, instead unmounted from ESXi hosts under the given cluster."
}
Write-Host " " ;
}
<#
.DESCRIPTION
Mount a VMFS datastore to all ESXi host(s) under the given vSphere cluster.
.PARAMETER ClusterName
vSphere Cluster name
.PARAMETER DatastoreName
Datastore name
.EXAMPLE
Mount-VmfsDatastore -ClusterName "myCluster" -DatastoreName "myDatastore"
.INPUTS
vCenter vSphere cluster name and datastore name.
.OUTPUTS
None.
#>
function Mount-VmfsDatastore {
[CmdletBinding()]
[AVSAttribute(10, UpdatesSDDC = $false, AutomationOnly = $true)]
Param (
[ Parameter(
Mandatory=$true,
HelpMessage = 'vSphere Cluster name in vCenter')]
[ValidateNotNull()]
[String]
$ClusterName,
[Parameter(
Mandatory=$true,
HelpMessage = 'Name of VMFS datastore to be mounted on host(s) in vCenter')]
[ValidateNotNull()]
[String]
$DatastoreName
)
$Cluster = Get-Cluster -Name $ClusterName -ErrorAction Ignore
if (-not $Cluster) {
throw "Cluster $ClusterName does not exist."
}
$Datastore = Get-Datastore -Name $DatastoreName -ErrorAction Ignore
if (-not $Datastore) {
throw "Datastore $DatastoreName does not exist."
}
if ("VMFS" -ne $Datastore.Type) {
throw "Datastore $DatastoreName is of type $($Datastore.Type). This cmdlet can only process VMFS datastores."
}
Write-Host "Mounting datastore $DatastoreName to all host(s) in the given vSphere cluster."
$HostViewDiskName = $Datastore.ExtensionData.Info.vmfs.extent[0].Diskname
if ($null -eq $HostViewDiskName){
throw "Could't find backing device for the datastore $($DatastoreName)"
}
$VmHosts = $Cluster | Get-VMHost
foreach ($VmHost in $VmHosts){
$Devices = $VmHost.ExtensionData.config.StorageDevice.ScsiLun | Where-Object { $_.DevicePath -like "*$($HostViewDiskName)*" }
if ($null -eq $Devices){
Write-Host "Could't find device on ESXi host $($VmHost.Name) for device UUID $($HostViewDiskName), skipping to mount datastore"
continue
}
$HostView = Get-View $VmHost
$StorageSys = Get-View $HostView.ConfigManager.StorageSystem
Write-Host "Mounting VMFS Datastore $($Datastore.Name) on host $($HostView.Name)"
try{
$StorageSys.MountVmfsVolume($Datastore.ExtensionData.Info.vmfs.uuid);
}
catch{
Write-Error "Failed to VMFS Datastore $($Datastore.Name) on host $($HostView.Name)"
}
Write-Host "Datastore $($Datastore.Name) mounted successfully on host, rescanning now.. $($hostview.Name)."
$VmHost | Get-VMHostStorage -RescanAllHba -RescanVmfs | Out-Null
}
}
<#
.SYNOPSIS
This function list all VMFS datastores accessible to host(s) under the given ESXi Cluster.
.PARAMETER ClusterName
vSphere Cluster Name
.EXAMPLE
Get-VmfsDatastore -ClusterName "vSphere-cluster-001"
.INPUTS
vSphere Cluster Name
.OUTPUTS
None.
#>
function Get-VmfsDatastore {
[CmdletBinding()]
[AVSAttribute(10, UpdatesSDDC = $false)]
Param
(
[Parameter(
Mandatory = $true,
HelpMessage = 'vSphere Cluster Name')]
[string] $ClusterName
)
Write-Host "Collecting all available VMFS datastores accessible to ESXi host(s) in the cluster " $ClusterName
Write-Host ""
$ClusterName = $ClusterName.Trim()
$Cluster = Get-Cluster -Name $ClusterName -ErrorAction Ignore
if (-not $Cluster) {
throw "Cluster $($ClusterName) does not exist."
}
$VmHosts = $Cluster | Get-VMHost -ErrorAction Ignore
if (-not $VmHosts) {
throw "No ESXi host found under $($ClusterName)."
}
$Datastores = Get-VMHost -Name $VmHosts | Get-Datastore | Where-Object {$_.Type -match "VMFS"} | Get-Unique
if ( -not $Datastores) {
Write-Host "No Datastore found under the given cluster."
return
}
$NamedOutputs = @{}
foreach ($Datastore in $Datastores){
$Hosts = Get-VMHost -Datastore $Datastore.Name | Select-Object select -ExpandProperty Name -ErrorAction Ignore
$VmfsUuid = $Datastore.ExtensionData.info.Vmfs.uuid
$HostViewDiskName = $Datastore.ExtensionData.Info.vmfs.extent[0].Diskname;
$NamedOutputs[$Datastore.Name] = "
{
Name : $($Datastore.Name),
Capacity : $($Datastore.CapacityGB),
FreeSpace : $($Datastore.FreeSpaceGB),
Type : $($Datastore.Type),
UUID : $($VmfsUuid),
Device : $($HostViewDiskName),
State : $($Datastore.State),
Hosts : $($Hosts),
}"
}
if($NamedOutputs.Count -gt 0){
Write-host $NamedOutputs | ConvertTo-Json -Depth 10
}
Set-Variable -Name NamedOutputs -Value $NamedOutputs -Scope Global
Write-Host " "
}
<#
.SYNOPSIS
This function collects all ESXi host(s) along with detailed inventory under a given vSphere Cluster.
.PARAMETER -ClusterName
vSphere Cluster Name
.EXAMPLE
Get-VmfsHosts -ClusterName "vSphere-cluster-001"
.INPUTS
vSphere Cluster Name
.OUTPUTS
NamedOutputs Detailed ESXi host(s) inventory
#>
function Get-VmfsHosts {
[CmdletBinding()]
[AVSAttribute(10, UpdatesSDDC = $false)]
Param
(
[Parameter(
Mandatory = $true,
HelpMessage = 'vSphere Cluster Name')]
[String] $ClusterName
)
Write-Host "Collecting detailed inventory of all ESXi host(s) under vSphere Cluster $($ClusterName), takes seconds .."
Write-Host " " ;
$Cluster = Get-Cluster -Name $ClusterName -ErrorAction Ignore
if (-not $Cluster) {
throw "Cluster $($ClusterName) does not exist."
}
$NamedOutputs = @{}
$VmHosts = $Cluster | Get-VMHost
foreach ($VmHost in $VmHosts) {
$Datastores = $VmHost | Get-Datastore | Where-Object { $_.Type -match "VMFS" } | Select-Object select -ExpandProperty Name
$NamedOutputs[$VmHost.Name] = "
{
Name : $($VmHost.Name),
Version : $($VmHost.Version),
ConnectionState : $($VmHost.ConnectionState),
PowerState : $($VmHost.PowerState),
State : $($VmHost.State),
HostNQN : $($VmHost.ExtensionData.Hardware.SystemInfo.QualifiedName.Value),
Uuid : $($VmHost.ExtensionData.Hardware.SystemInfo.Uuid),
Datastores: $($Datastores),
Extension : $($VmHost.ExtensionData.config.StorageDevice.NvmeTopology | ConvertTo-JSON -Depth 2)
}"
}
Set-Variable -Name NamedOutputs -Value $NamedOutputs -Scope Global
Write-Host ""
}
<#
.SYNOPSIS
This function collects Storage Adapter info for ESXi host(s) in a given vSphere Cluster.
.PARAMETER -ClusterName
vSphere Cluster Name
.EXAMPLE
Get-StorageAdapters -ClusterName "vSphere-cluster-001"
.INPUTS
vSphere Cluster Name
.OUTPUTS
NamedOutputs detailed storage adapters inventory
#>
function Get-StorageAdapters {
[CmdletBinding()]
[AVSAttribute(10, UpdatesSDDC = $false)]
Param
(
[Parameter(
Mandatory = $true,
HelpMessage = 'vSphere Cluster Name')]
[String] $ClusterName
)
Write-Host "Collecting storage adapters inventory of all ESXi host(s) under vSphere Cluster $($ClusterName)"
Write-Host " " ;
$Cluster = Get-Cluster -Name $ClusterName -ErrorAction Ignore
if (-not $Cluster) {
throw "Cluster $($ClusterName) does not exist."
}
$NamedOutputs = @{}
$VmHosts = $Cluster | Get-VMHost
foreach ($VmHost in $VmHosts) {
$Adapters = $Null
try {
$Adapters = Get-VMHostHba -VMHost $VmHost.Name -ErrorAction Ignore
}
catch {
Write-Error "Failed to collect VMKernel Info on host $($VmHost.Name), continue collecting about rest of the host(s)."
continue
}
$StorageAdapters = New-Object System.Collections.ArrayList
if (-not $Adapters) {
continue
}
foreach ($Adapter in $Adapters) {
$St = $Adapter | Select-Object -Property * -ExcludeProperty "VMHost"
$StorageAdapters.Add($St) | Out-Null
}
$NamedOutputs.Add($VmHost.Name.Trim(), ($StorageAdapters | ConvertTo-Json -Depth 2))
}
Set-Variable -Name NamedOutputs -Value $NamedOutputs -Scope Global
Write-Host ""
}
<#
.SYNOPSIS
This function collects storage vmkernel adapter for ESXi host(s) in a given vSphere Cluster.
.PARAMETER -ClusterName
vSphere Cluster Name
.EXAMPLE
Get-VmKernelAdapters -ClusterName "vSphere-cluster-001"
.INPUTS
vSphere Cluster Name
.OUTPUTS
NamedOutputs detailed storage vmkernel adapters inventory.
#>
function Get-VmKernelAdapters {
[CmdletBinding()]
[AVSAttribute(10, UpdatesSDDC = $false)]
Param
(
[Parameter(
Mandatory = $true,
HelpMessage = 'vSphere Cluster Name')]
[String] $ClusterName
)
Write-Host "Collecting VMKernel adapters inventory of all ESXi host(s) under vSphere Cluster $($ClusterName)"
Write-Host " " ;
$Cluster = Get-Cluster -Name $ClusterName -ErrorAction Ignore
if (-not $Cluster) {
throw "Cluster $($ClusterName) does not exist."
}
$NamedOutputs = @{}
$VmHosts = $Cluster | Get-VMHost
foreach ($VmHost in $VmHosts) {
$KernelAdapters = $null
try {
$KernelAdapters = Get-VMHostNetworkAdapter -VMHost $VmHost.Name -VMKernel
}
catch {
Write-Error "Failed to collect VMKernel info on host $($VmHost.Name), continue collecting from other host(s)."
continue
}
$VmKernelAdapters = New-Object System.Collections.ArrayList
if (-not $KernelAdapters) {
continue
}
foreach ($Adapter in $KernelAdapters) {
$Vmk = $Adapter | Select-Object -Property * -ExcludeProperty "VMHost"
$VmKernelAdapters.Add($Vmk) | Out-Null
}
$NamedOutputs.Add($VmHost.Name.Trim(), ($VmKernelAdapters | ConvertTo-Json -Depth 2))
}
Set-Variable -Name NamedOutputs -Value $NamedOutputs -Scope Global
Write-Host ""
}
<#
.SYNOPSIS
This function enables NVMeTCP storage services on given vmkernel adapter for a host.
.PARAMETER -HostAddress
ESXi host network address
.PARAMETER VmKernel
Storage VMKernel name
.EXAMPLE
Set-NVMeTCP -HostAddress "192.168.10.11" -VmKernel "vmk0"
.INPUTS
ESXi host network address
Storage VMKernel name
.OUTPUTS
NamedOutputs operation result.
#>
function Set-NVMeTCP {
[CmdletBinding()]
[AVSAttribute(10, UpdatesSDDC = $false, AutomationOnly = $true)]
Param
(
[Parameter(
Mandatory = $true,
HelpMessage = 'ESXi host network address')]
[String] $HostAddress,
[Parameter(
Mandatory = $true,
HelpMessage = 'Existing VMKernel adapter name')]
[String] $VmKernel
)
Write-Host "Enabling NVMeTCP services on given VMKernal adapter for host $($HostAddress)"
Write-Host " " ;
$VmHost = Get-VMHost -Name $HostAddress -ErrorAction Ignore
if (-not $VmHost) {
throw "ESXi $($HostAddress) does not exist."
}
$VmKernel = $VmKernel.Trim()
$NamedOutputs = @{}
$KernelAdapters = $null
try {
$KernelAdapters = Get-VMHostNetworkAdapter -VMHost $VmHost.Name -VMKernel -Name $VmKernel
}
catch {
Write-Host "Failed to collect VMKernel adapters controller $($_.Exception)"
throw "Failed to collect VMKernel adapters controller $($_.Exception)"
}
if (-not $KernelAdapters -or $KernelAdapters.Count -eq 0) {
throw "Didn't find VMKernel adapters on host"
}
$HostEsxcli = Get-EsxCli -VMHost $VmHost.Name -ErrorAction stop
$isEnabled = $HostEsxcli.network.ip.interface.tag.add($VmKernel, 'NVMeTCP')
if ($isEnabled) {
Get-VMHostStorage -VMHost $HostAddress -RescanAllHba | Out-Null
$NamedOutputs.Add($VmKernel, "NVMe/TCP Service enabled successfully.")
}
else {
$NamedOutputs.Add($VmKernel, "Failed to enable NVMe/TCP Service on host.")
}
if ($NamedOutputs.Count -gt 0) {
Write-host $NamedOutputs | ConvertTo-Json -Depth 10
}
Set-Variable -Name NamedOutputs -Value $NamedOutputs -Scope Global
Write-Host ""
}
<#
.SYNOPSIS
This function creates new NVMe/TCP storage adapter on given ESXi host.
.PARAMETER -HostAddress
ESXi host network address
.PARAMETER VmKernel
Storage Nic name
.EXAMPLE
New-NVMeTCPAdapter -HostAddress "192.168.10.11" -VmNic "vmnic0"
.INPUTS
ESXi host network address
Storage NIC name
.OUTPUTS
NamedOutputs operation result.
#>
function New-NVMeTCPAdapter {
[CmdletBinding()]
[AVSAttribute(10, UpdatesSDDC = $false, AutomationOnly = $true)]
Param
(
[Parameter(
Mandatory = $true,
HelpMessage = 'ESXi host network address')]
[String] $HostAddress,
[Parameter(
Mandatory = $true,
HelpMessage = 'Existing Physical NIC name')]
[String] $VmNic
)
Write-Host "Creating a new NVMe/TCP adapter using storage nic on host $($HostAddress)"
Write-Host " " ;
$VmHost = Get-VMHost -Name $HostAddress -ErrorAction Ignore
if (-not $VmHost) {
throw "ESXi $($HostAddress) does not exist."
}
$VmNic = $VmNic.Trim()
$NamedOutputs = @{}
$Nics = $null
try {
$Nics = Get-VMHostNetworkAdapter -VMHost $VmHost.Name -Physical -Name $VmNic
}
catch {
Write-Host "Failed to collect physical inventory Nic $($_.Exception)"
throw "Failed to collect physical inventory Nic $($_.Exception)"
}
if (-not $Nics -or $Nics.Count -eq 0) {
throw "Didn't find Nic adapters on host"
}
$HostEsxcli = Get-EsxCli -VMHost $VmHost.Name -ErrorAction stop
$IsCreated = $HostEsxcli.nvme.fabrics.enable($VmNic, 'TCP');
if ($IsCreated) {
$NamedOutputs.Add($VmNic, "NVMe/TCP adapter created successfully.")
Get-VMHostStorage -VMHost $HostAddress -RescanAllHba | Out-Null
}
else {
$NamedOutputs.Add($VmNic, "Failed to create NVMe/TCP adapter.")
}
if ($NamedOutputs.Count -gt 0) {
Write-host $NamedOutputs | ConvertTo-Json -Depth 10
}
Set-Variable -Name NamedOutputs -Value $NamedOutputs -Scope Global
Write-Host ""
}
<#
.SYNOPSIS
This function collects all VMs on the provided datastore and creates snapshot of each virtual machine.
.PARAMETER -ClusterName
vSphere Cluster Name
.PARAMETER -DatastoreName
Datastore name
.EXAMPLE
New-VmfsVmSnapshot -ClusterName "vSphere-cluster-001" -DatastoreName "myDatastore"
.INPUTS
vSphere cluster name, datastore name
.OUTPUTS
None.
#>
function New-VmfsVmSnapshot {
[CmdletBinding()]
[AVSAttribute(10, UpdatesSDDC = $false, AutomationOnly = $true)]
Param
(
[Parameter(
Mandatory = $true,
HelpMessage = 'vSphere Cluster Name')]
[String] $ClusterName,
[Parameter(
Mandatory = $true,
HelpMessage = 'datastore name')]
[String] $datastoreName
)
Write-Host "Creating snapshot of all VMs on the given datastore accessible to cluster $($ClusterName)"
Write-Host " " ;
$Cluster = Get-Cluster -Name $ClusterName -ErrorAction Ignore
if (-not $Cluster) {
throw "Cluster $($ClusterName) does not exist."
}
$Datastore = Get-Datastore -Name $DatastoreName -RelatedObject $Cluster -ErrorAction Ignore
if (-not $Datastore) {
throw "Datastore $DatastoreName does not exist."
}
$NamedOutputs = @{}
$Vms = Get-VM -Datastore $Datastore
foreach ($Vm in $Vms) {
$timeStamp = Get-Date -Format o | ForEach-Object { $_ -replace ":", "-" }
$SnapshotName = $Vm.Name + "-" + $timeStamp
if (!$Vm.ExtensionData) {
Write-Host "Skipping to create snapshot of virtual machine $($Vm.Name), becuase of unavailable configuration."
continue
}
if (($Vm.ExtensionData.OverallStatus -ne "green") -or ($Vm.ExtensionData.guestHeartbeatStatus -ne "green")) {
Write-Host "Skipping to create snapshot of virtual machine $($Vm.Name) becuase health status is not OK."
continue
}
try {
$Snapshot = New-Snapshot -VM $Vm -Quiesce -Name $SnapshotName -ErrorAction Ignore
Write-Host "Snapshot $($Snapshot.Name) created."
}
catch {
Write-Host "Failed to creat snapshot for $($Vm.Name) $($_.Exception)"
throw "Failed to creat snapshot for $($Vm.Name) $($_.Exception)"
}
}
Set-Variable -Name NamedOutputs -Value $NamedOutputs -Scope Global
Write-Host ""
}
<#
.SYNOPSIS
This function repairs HA configuration on all hosts in a given vSphere Cluster.
.PARAMETER ClusterName
vSphere Cluster Name
.EXAMPLE
Repair-HAConfiguration -ClusterName "vSphere-cluster-001"
.INPUTS
vSphere Cluster Name
#>
function Repair-HAConfiguration {
[CmdletBinding()]
[AVSAttribute(10, UpdatesSDDC = $false, AutomationOnly = $true)]
Param
(
[Parameter(
Mandatory = $true,
HelpMessage = 'vSphere Cluster Name')]
[String] $ClusterName
)
$Cluster = Get-Cluster -Name $ClusterName -ErrorAction Ignore
if (-not $Cluster) {
throw "Cluster $($ClusterName) does not exist."
}
$VMHosts = $null
try {
$VMHosts = Get-Cluster $ClusterName | Get-VMHost
}
catch {
Write-Host "Failed to collect cluster hosts $($_.Exception)"
throw "Failed to collect cluster hosts $($_.Exception)"
}
$Success = $true
foreach ($VMHost in $VMHosts) {
$HostAddress = $VMHost.Name
Write-Host "Repairing HA configuration on host $HostAddress"
try {
$VMHost.ExtensionData.ReconfigureHostForDAS()
} catch {
Write-Error "Failed to repair HA configuration on host $HostAddress"
$Success = $false
}
}
if (-not $Success) {
throw "Failed to repair HA configuration on one or more hosts"
}
}
<#
.SYNOPSIS
This function clears disconnected iSCSI targets on all hosts in a given vSphere Cluster.
.PARAMETER ClusterName
vSphere Cluster Name
.PARAMETER VMHostName
ESXi host name. If not specified, all hosts in the cluster will be used.
.EXAMPLE
Clear-DisconnectedIscsiTargets -ClusterName "vSphere-cluster-001"
.INPUTS
vSphere Cluster Name
#>
function Clear-DisconnectedIscsiTargets {
[CmdletBinding()]
[AVSAttribute(10, UpdatesSDDC = $false, AutomationOnly = $true)]
Param
(
[Parameter(
Mandatory = $true,
HelpMessage = 'vSphere Cluster Name')]
[String] $ClusterName,
[Parameter(
Mandatory = $false,
HelpMessage = 'VMHost Name')]
[String] $VMHostName
)
$Cluster = Get-Cluster -Name $ClusterName -ErrorAction Ignore
if (-not $Cluster) {
throw "Cluster $($ClusterName) does not exist."
}
$VMHosts = $null
try {
if ($VMHostName) {
$VMHosts = $Cluster | Get-VMHost -Name $VMHostName
} else {
$VMHosts = $Cluster | Get-VMHost
}
}
catch {
throw "Failed to collect cluster hosts $($_.Exception)"
}
if (-not $VMHosts) {
throw "No matching hosts found in cluster $ClusterName"
}
foreach ($VMHost in $VMHosts) {
$IscsiTargetRemoved = $false
$HostAddress = $VMHost.Name
Write-Host "Clearing disconnected iSCSI targets on host $HostAddress"
$EsxCli = Get-EsxCli -VMHost $VMHost.Name -V2
$DisconnectedSessions = $Esxcli.iscsi.session.connection.list.Invoke() | Where-Object { $_.State.Trim() -ne "logged_in" }
foreach ($session in $DisconnectedSessions) {
try {
Write-Host "Clearing disconnected iSCSI target $($session.ConnectionAddress) on host $HostAddress"
$targets = Get-IScsiHbaTarget | Where-Object { $_.Address -eq $session.ConnectionAddress }
$targets | Remove-IScsiHbaTarget -Confirm:$false
$IscsiTargetRemoved = $true
}
catch {
Write-Error "Failed to clear disconnected iSCSI targets on host $HostAddress"
}
}
if ($IscsiTargetRemoved) {
Write-Host "Rescanning storage on host $HostAddress"
$VMHost | Get-VMHostStorage -RescanAllHba -RescanVmfs
}
}
}
<#
.SYNOPSIS
This function checks each cluster host connectivity to vmkernel interface
.PARAMETER ClusterName
vSphere Cluster Name
.EXAMPLE
Test-VMKernelConnectivity -ClusterName "vSphere-cluster-001"
.INPUTS
vSphere Cluster Name, Storage VMKernel name
#>
function Test-VMKernelConnectivity {
[CmdletBinding()]
[AVSAttribute(10, UpdatesSDDC = $false, AutomationOnly = $true)]
Param
(
[Parameter(
Mandatory = $true,
HelpMessage = 'vSphere Cluster Name')]
[String] $ClusterName
)
$Cluster = Get-Cluster -Name $ClusterName -ErrorAction Ignore
if (-not $Cluster) {
throw "Cluster $($ClusterName) does not exist."
}
$VMHosts = $null
try {
$VMHosts = Get-Cluster $ClusterName | Get-VMHost
}
catch {
Write-Host "Failed to collect cluster hosts $($_.Exception)"
throw "Failed to collect cluster hosts $($_.Exception)"
}
$Success = $true
foreach ($VMHost in $VMHosts) {
$HostAddress = $VMHost.Name
$NetworkInterfaces = Get-VMHostNetworkAdapter -VMHost $VMHost | Where-Object {$_.Ip}
foreach ($Nic in $NetworkInterfaces) {
Write-Host "Checking connectivity to vmkernel interface $($Nic.Name) with address $($Nic.IP) on host $HostAddress"
$esxcli = Get-EsxCli -VMHost $VMHost.Name -V2
$params = $esxcli.network.diag.ping.CreateArgs()
$params.host = $Nic.IP
$result = $esxcli.network.diag.ping.Invoke($params)
if ($result.Summary.Received -gt 0) {
Write-Host "Ping to vmkernel interface $($VmKernel) on host $HostAddress is successful"
}
else {
Write-Error "Ping to vmkernel interface $($VmKernel) on host $HostAddress failed"
$Success = $false
}
}
}
if (-not $Success) {
throw "Ping to vmkernel interface failed on one or more hosts"
}
}