The Lowercase w

Post info:

PowerCLI script to forcibly delete a Datastore that is reported as in use.

Have you ever tried to delete a datastore and get an error saying the Datastore is in use? You browse the datastore and see nothing on it. The pre-checks when you unmount it all pass, yet you cannot delete it. We had this problem.

VMware support directed us to delete the partition using the partedUtil command, but I didn’t like the possibility of human error in doing it their way. I wrote a PowerCLI script that performs the following.
1. Connect to vCenter. (choose from a list if necessary)
2. Choose which Cluster has a host with the datastore.
3. Choose a host that the datastore is presented to.
4. Displays the contents of the datastore (same as BROWSE DATASTORE.
5. Validates to make sure the datastore doesn’t have sDRS turned on, has no VMs, no vD Swap File, not being used for HA Heartbeat, no SOIC, and doesn’t contain a scratch partition. This step utilizes a function written by Luc Dekens (@lucd22).
6. Displays the Runtime Name which includes the LUN ID to verify.
7. Displays the Canonical Name of the LUN to verify.
8. It then displays a warning that future steps will perform DESTRUCTIVE ACTIONS.
9. It then unmounts the datastore from all hosts that it’s currently mounted to.
10. SSH is started on the host in Step 3.
You’ll need to go into the code and enter the ID/PW.
11. It connects via SSH to the server you choose in STEP 3 and runs partedUtil getptbl to list the partitions on the device.
12. It DELETES the partition listed in STEP 10.
13. It runs partedUtil getptbl again to verify the partition has been removed.
14. SSH is stopped.
15. The datastore is DELETED/REMOVED.
16. The get-datastore command is run to verify it’s been removed (You should see RED TEXT when this runs (aka blood splatter).
16. It disconnects from vCenter.

 

In the documentation I wrote up for my team members, I put the following prerequisites…

– Delete all files (except the .sdd.sf folder) from the datastore before running the script.
– plink.exe should be stored in the root of the C drive.
– If at ANY POINT in the script something looks wrong, press CTRL-C to cancel the script

 

#################################################
#
# PowerCLI Script to Remove a datastore
# Created by BLiebowitz on 7/12/2017
# 
#################################################

# Define the LunDatastoreName 
# I found this function from: http://www.vsysad.com/2013/07/powercli-script-to-get-naa-and-datastore-name-of-all-storage-on-esx-server/
         function</pre><pre>	New-VIProperty -Name lunDatastoreName -ObjectType ScsiLun -Value {
		param($lun)
	 
		$ds = $lun.VMHost.ExtensionData.Datastore | %{Get-View $_} | `
			where {$_.Summary.Type -eq "VMFS" -and
				($_.Info.Vmfs.Extent | where {$_.DiskName -eq $lun.CanonicalName})}
		if($ds){
			$ds.Name
		}
	} -Force | Out-Null

# Build Pause Function
	Function Pause($M="Press any key to continue . . . "){If($psISE){$S=New-Object -ComObject "WScript.Shell";$B=$S.Popup("Click OK to continue.",0,"Script Paused",0);Return};Write-Host -NoNewline $M;$I=16,17,18,20,91,92,93,144,145,166,167,168,169,170,171,172,173,174,175,176,177,178,179,180,181,182,183;While($K.VirtualKeyCode -Eq $Null -Or $I -Contains $K.VirtualKeyCode){$K=$Host.UI.RawUI.ReadKey("NoEcho,IncludeKeyDown")};Write-Host}	
	
# Define the GetStatstoreUnmountStatus Function
# function courtesy of Luc Dekens: http://www.lucd.info/2012/04/15/test-if-the-datastore-can-be-unmounted/
	function Get-DatastoreUnmountStatus{
	<# .SYNOPSIS Check if a datastore can be unmounted. .DESCRIPTION The function checks a number of prerequisites that need to be met to be able to unmount a datastore. .NOTES Author: Luc Dekens .PARAMETER Datastore The datastore for which you want to chekc the conditions. You can pass the name of the datastore or the Datastore object returned by Get-Datastore .EXAMPLE PS> Get-DatastoreUnmountStatus -Datastore DS1
	.EXAMPLE
	PS> Get-Datastore | Get-DatastoreUnmountStatus
	#>
	param(
	[CmdletBinding()]
	[Parameter(Mandatory=$true,ValueFromPipeline=$true)]
	[PSObject[]]$Datastore
	)
	process{
	foreach($ds in $Datastore){
	if($ds.GetType().Name -eq "string"){
	$ds = Get-Datastore -Name $ds
	}
	$parent = Get-View $ds.ExtensionData.Parent
	New-Object PSObject -Property @{
	Datastore = $ds.Name
	# No Virtual machines
	NoVM = $ds.ExtensionData.VM.Count -eq 0
	# Not in a Datastore Cluster
	NoDastoreClusterMember = $parent -isnot [VMware.Vim.StoragePod]
	# Not managed by sDRS
	NosDRS = &{
	if($parent -is [VMware.Vim.StoragePod]){
	!$parent.PodStorageDrsEntry.StorageDrsConfig.PodConfig.Enabled
	}
	else {$true}
	}
	# SIOC disabled
	NoSIOC = !$ds.StorageIOControlEnabled
	# No HA heartbeat
	NoHAheartbeat = &{
	$hbDatastores = @()
	$cls = Get-View -ViewType ClusterComputeResource -Property Host |
	where{$_.Host -contains $ds.ExtensionData.Host[0].Key}
	if($cls){
	$cls | %{
	(                $_.RetrieveDasAdvancedRuntimeInfo()).HeartbeatDatastoreInfo | %{
	$hbDatastores += $_.Datastore
	}
	}
	$hbDatastores -notcontains $ds.ExtensionData.MoRef
	}
	else{$true}
	}
	# No vdSW file
	NovdSwFile = &{
	New-PSDrive -Location $ds -Name ds -PSProvider VimDatastore -Root '\' | Out-Null
	$result = Get-ChildItem -Path ds:\ -Recurse |
	where {$_.Name -match '.dvsData'}
	Remove-PSDrive -Name ds -Confirm:$false
	if($result){$false}else{$true}
	}
	# No scratch partition
	NoScratchPartition = &{
	$result = $true
	$ds.ExtensionData.Host | %{Get-View $_.Key} | %{
	$diagSys = Get-View $_.ConfigManager.DiagnosticSystem
	$dsDisks = $ds.ExtensionData.Info.Vmfs.Extent | %{$_.DiskName}
	if($dsDisks -contains $diagSys.ActivePartition.Id.DiskName){
	$result = $false
	}
	}
	$result
	}
	}
	}
	}
	}

# Define the Detach Datastore Function
# This function is courtsey of William Lan: https://blogs.vmware.com/vsphere/2012/01/automating-datastore-storage-device-detachment-in-vsphere-5.html
	Function Detach-Datastore {
		[CmdletBinding()]
		Param (
			[Parameter(ValueFromPipeline=$true)]
			$Datastore
		)
		Process {
			if (-not $Datastore) {
				Write-Host "No Datastore defined as input"
				Exit
			}
			Foreach ($ds in $Datastore) {
				$hostviewDSDiskName = $ds.ExtensionData.Info.vmfs.extent[0].Diskname
				if ($ds.ExtensionData.Host) {
					$attachedHosts = $ds.ExtensionData.Host
					Foreach ($VMHost in $attachedHosts) {
						$hostview = Get-View $VMHost.Key
						$StorageSys = Get-View $HostView.ConfigManager.StorageSystem
						$devices = $StorageSys.StorageDeviceInfo.ScsiLun
						Foreach ($device in $devices) {
							if ($device.canonicalName -eq $hostviewDSDiskName) {
								$LunUUID = $Device.Uuid
								Write-Host "Detaching LUN $($Device.CanonicalName) from host $($hostview.Name)..."
								$StorageSys.DetachScsiLun($LunUUID);
							}
						}
					}
				}
			}
		}
	}

# Define the Unmount Datastore Function
# This function is courtsey of William Lan: https://blogs.vmware.com/vsphere/2012/01/automating-datastore-storage-device-detachment-in-vsphere-5.html
	Function Unmount-Datastore {
		[CmdletBinding()]
		Param (
			[Parameter(ValueFromPipeline=$true)]
			$Datastore
		)
		Process {
			if (-not $Datastore) {
				Write-Host "No Datastore defined as input"
				Exit
			}
			Foreach ($ds in $Datastore) {
				$hostviewDSDiskName = $ds.ExtensionData.Info.vmfs.extent[0].Diskname
				if ($ds.ExtensionData.Host) {
					$attachedHosts = $ds.ExtensionData.Host
					Foreach ($VMHost in $attachedHosts) {
						$hostview = Get-View $VMHost.Key
						$StorageSys = Get-View $HostView.ConfigManager.StorageSystem
						Write-Host "Unmounting VMFS Datastore $($DS.Name) from host $($hostview.Name)..."
						$StorageSys.UnmountVmfsVolume($DS.ExtensionData.Info.vmfs.uuid);
					}
				}
			}
		}
	}	
	
# Select which vCenter you want to connect to
# Build array for each vCenter
	Write-host "Select which vCenter to connect to:"
	Write-Host ""
	Write-Host "1. vCenter1"
	Write-Host "2. vCenter2"
	Write-Host "3. vCenter3"

	$Ivcenter = read-host "Select a vCenter Server. Enter Number."
	 
	if ($Ivcenter -match "1") {
	$vcenter = "vCenter1"
	} elseif ($Ivcenter -match "2") {
	$vcenter = "vCenter2"
	} else {
	$vcenter = "vCenter3"
	} 
	 
	write-host ""
	Write-Host "You Picked: $($vcenter)"
	write-host ""
	start-sleep -s 3

# connect to selected vCenter
	connect-viserver $vcenter

# List Clusters
	write-host ""
	Write-host "Choose which Cluster where the LUN you want to remove presented:"
	write-host "(it may take a few seconds to build the list)"
	write-host ""
	$ICLUSTER = get-cluster | Select Name | Sort-object Name
	$i = 1
	$ICLUSTER | %{Write-Host $i":" $_.Name; $i++}
	$HCLUSTER = Read-host "Enter the number for the host to Patch."
	$SCLUSTER = $ICLUSTER[$HCLUSTER -1].Name
	write-host "You have selected $($SCLUSTER)."
	start-sleep -s 3
	
# List hosts to select
	write-host ""
	Write-host "Choose which vSphere host that has the LUN presented."
	write-host "(it may take a few seconds to build the list)"
	write-host ""
	$IHOST = get-cluster $SCLUSTER | Get-VMhost | Select Name | Sort-object Name
	$i = 1
	$IHOST | %{Write-Host $i":" $_.Name; $i++}
	$DSHost = Read-host "Enter the number for the host to Patch."
	$SHOST = $IHOST[$DSHost -1].Name
	write-host "you have selected" $SHOST"."	

# List all scsi devices
	write-host ""
	Write-host "Choose which LUN to remove.  *** VERIFY VIA THE vSPHERE CLIENT ***"
	write-host "(it may take a minute to build the list)"
	write-host ""
	$ILUN = get-scsilun -vmhost $SHOST | Select LunDatastoreName, CanonicalName, RuntimeName, CapacityGB | Sort lundatastorename
	$i = 1
	$ILUN | %{Write-Host $i":" $_.lunDatastoreName, $_.RuntimeName, $_.CapacityGB; $i++}
	$DSLUN = Read-host "Enter the number for the LUN to remove."
	$SLUN = $ILUN[$DSLUN -1].LunDatastoreName
	write-host "you have selected" $SLUN"."

# List the files on the LUN you selected
	get-childitem (get-datastore $SLUN).datastorebrowserpath | Select FullName

# Pause for input while you verify the Datastore Contents.
	Write-host "Verify there is nothing on the datastore."
	Write-Host "FYI - The .sdd.sf Folder cannot be removed."
	Pause
	
# Check to see if the datastore is in use
	Get-DatastoreUnmountStatus -datastore $SLUN
	
# Pause for input while datastore unmount status is verified.
	Write-Host "Verify the datastore passes unmount verification."  
	Write-host "All should be set to TRUE"
	Pause
	
# Verify the LUN ID is correct
	Write-Host ""
	Get-Datastore $SLUN | get-scsilun | Select -First 1 -ExpandProperty RuntimeName	
	Write-Host ""
	
# Pause for input while the LUN ID is verified
	Write-Host "Verify the last characters of the LUN ID match."
	Pause
	
# Pipe datastore to get-scsilun to get canonicalname
	$LUN = Get-Datastore $SLUN | get-scsilun | Select -First 1 -ExpandProperty CanonicalName
	Write-Host "The CanonicalName of the Datastore is $($LUN)"
	
# Warn about future steps will perform destructive actions
	Write-Host ""
	Write-Host ""
	Write-Host "WARNING:  All Steps from here on out will perform DESTRUCTIVE ACTIONS!!"
	Write-Host "Press CTRL-C to cancel the script or any key to continue"
	Write-Host ""
	Write-Host ""
	Pause

# Unmount the Datastore
	get-datastore $SLUN | Unmount-Datastore
	
# Pause and verify the Datastore was unmounted successfully
	Write-host "Verify Datastore $($SLUN) Unmounted successfully"
	Pause
	
# Start SSH
	Write-Host "Starting SSH"
	$ssh = Get-VMHostService -vmhost $SHOST | Where {$_.Key -eq "TSM-SSH"}
	Start-VMHostService -HostService ($ssh) -Confirm:$false 

# Verify SSH started successfully	
	Write-Host "Verify SSH started successfully."
	Pause

# Validate and delete the partition
	$User = "root"
	$Pswd = "password"
	$plink = "c:\plink.exe"

	$plinkoptions = " -v -batch -pw $Pswd"
	$cmd1 = 'partedUtil getptbl /vmfs/devices/disks/$LUN'

	$remoteCommand = '"' + $cmd1 + '"'
	$command = $plink + " " + $plinkoptions + " " + $User + "@" + $SHOST + " " + $remoteCommand

	$msg = Invoke-Expression -command $command
	Write-Host "This command displays the partitions on the LUN."
	$msg
	Pause

	$cmd1 = "partedUtil delete /vmfs/devices/disks/$LUN 1"
	
	$remoteCommand = '"' + $cmd1 + '"'
	$command = $plink + " " + $plinkoptions + " " + $User + "@" + $SHOST + " " + $remoteCommand
	
	$msg = Invoke-Expression -command $command
	
	Write-Host "This command DELETES the partition on the LUN."
	$msg
	Pause
	
	$cmd1 = 'partedUtil getptbl /vmfs/devices/disks/$LUN'

	$remoteCommand = '"' + $cmd1 + '"'
	$command = $plink + " " + $plinkoptions + " " + $User + "@" + $SHOST + " " + $remoteCommand

	$msg = Invoke-Expression -command $command
	write-host "This command verifies the partition has been removed."
	$msg
	Pause
	
# Stop SSH
	Write-Host "Stopping SSH"
	Stop-VMHostService -HostService ($ssh) -Confirm:$false 	
	
# Detach the LUN
	Get-datastore $SLUN | Detach-Datastore  

# Pause and verify the Datastore was Detached successfully
	Write-host "Verify Datastore $($SLUN) Detached successfully"
	Pause
	
# Delete Datastore
	get-datastore $SLUN | get-vmhost | remove-datastore $SLUN -confirm:$false
	
# Verify Datastore is removed
	Write-Host "Verify the datastore has been removed."
	Write-Host "There SHOULD be red text (aka blood spatter) below."
	get-datastore $SLUN 
	
# Disconnect from vCenter
	disconnect-viserver $vcenter -confirm:$false
	

Use this wisely. As I said, it performs a destructive action.

Ben Liebowitz, VCP, vExpert
NJ VMUG Leader

2 thoughts on “PowerCLI script to forcibly delete a Datastore that is reported as in use.

Leave a Reply

Your email address will not be published. Required fields are marked *