Check root password with PowerCLI (Multi-threading!)

Keeping your root password similar in all of your ESXi hosts, is one of the virtual environment key methods to control and maintain large environments. It will make it easier to connect directly to a host in case of vCenter failure, access SSH for troubleshooting and control it from DCUI.

There is a great one-liner by Kelvin Wong, that allows you to get a list of all of the VMhosts that have different password than the standard one.

In this post, I’ll try to:

  1. Make it simpler for PowerCLI beginners to use this script
  2. Provide advanced users with methods of multi-threading in PowerShell 2.0

First, the script for getting a list of hosts with non standard password :

$vCenterName = "vcenter.company.corp"  # ChangeME
$ExportFileLocation = "F:\Scripts\Harel_CheckrootPassword\PasswordNotMatch.txt" # ChangeME
$rootpassword = Read-host -assecurestring -prompt "Please enter local root password"
 
if (!(get-pssnapin -name VMware.VimAutomation.Core -erroraction silentlycontinue)) {
    add-pssnapin VMware.VimAutomation.Core}
 
Connect-VIServer $vCenterName

$vmhostsView = get-view -ViewType HostSystem -Property Name,Summary.runtime.ConnectionState `
| Where {$_.Summary.runtime.ConnectionState -eq "connected"} | %{$_.Name}

if (Test-Path  ($ExportFileLocation) -pathtype leaf)
{Remove-Item $ExportFileLocation -Confirm:$False}

$vmhostsView | %{ $err = @() ; 
connect-viserver $_ -user root -password $rootpassword -EA silentlycontinue -EV err ; 
if ($err.count -gt 0) { $_ | out-file $ExportFileLocation -append }
else {disconnect-viserver $_ -force -confirm:$false} }

The file created by this script, contains a list of hosts with root password different than the one typed as input.

BUT, the script will test connection to each of the hosts one-by-one, which may take a while if you have more than 10 hosts. In an environment of 74 hosts, for example, it took the script 10:30 min to run (calculated with measure-command of course):

serial-10.30

We will reduce the run time of the script, by using parallelism of the hosts check. Multi-threading in PowerShell.

The script will start the same way as the one above –

$vCenterName = "vcenter.company.corp"  # ChangeME
$ExportFileLocation = "F:\Scripts\Harel_CheckrootPassword\PasswordNotMatch.txt" # ChangeME
$rootpassword = Read-host -assecurestring -prompt "Please enter local root password"
 
if (!(get-pssnapin -name VMware.VimAutomation.Core -erroraction silentlycontinue)) {
    add-pssnapin VMware.VimAutomation.Core}
 
Connect-VIServer $vCenterName

$vmhostsView = get-view -ViewType HostSystem -Property Name,Summary.runtime.ConnectionState `
| Where {$_.Summary.runtime.ConnectionState -eq "connected"} | %{$_.Name}

if (Test-Path  ($ExportFileLocation) -pathtype leaf)
{Remove-Item $ExportFileLocation -Confirm:$False}

And now, adding the interesting part –

function CanWeAddJob(){
$Alljobs=(Get-Job -State "Running" | measure-Object).count
if ($alljobs -lt 10) {return $true}
else {return $false}
}

Foreach ($vmhostname in $vmhostsView){
Do {Start-Sleep -Milliseconds 500}
while (!(CanWeAddJob))

$args = ($vmhostname,$credentials,$ExportFileLocation)

Start-Job -Name $vmhostname -ArgumentList $args -InitializationScript {Add-PSSnapin VMware.VimAutomation.Core} –Scriptblock {$err = @() ;
Connect-VIServer $args[0] -Credential $args[1] -EA silentlycontinue -EV err ; 
if ($err.count -gt 0) { $args[0] | out-file $args[2] -append } 
else {disconnect-viserver $_ -force -confirm:$false} } -RunAs32
}

Explanation:

Function CanWeAddJob() is checking what is the running job count in your PowerShell session, and determine whether to add more job, or not. In this example, it does it to a maximum of 10 parallel jobs.

Foreach is here to split the long VMHosts list to many separated tasks. It will only add task to the running job queue, if the queue have less than 10 jobs.
PSSnapin was added to each of the new powershell.exe instances created, with RunAs32 parameter, to make it take less RAM of your server / workstation.

While the script is running, you should see something like this in your task manager:

task-manager

Can you guess what was the run time of the multi-threaded script?

parallel-2.42

2:42 min, which saved me 75% of the original run time.

Detach All Unused Devices in ESXi Cluster

Removing storage devices (Datastores + RDMs) can be tricky task for the VI admin. He will have to:

  1. Map the datastores / RDMs in use (maybe with PowerCLI scripts, EMC VSI, or other storage plugins)
  2. Delete / Remove the specific devices from virtual environment side (Delete datastore, Remove RDM from VM)
  3. Detach the devices from ESXi hosts
  4. Let the storage admin know this devices can be removed from storage side

I’m here to help you with automation for the 3rd task. The amount of steps required for detaching 1 device off 10 hosts cluster, is 10 steps (or 10 detach operations). Detaching 20 devices off this cluster will result in 200 steps, which can be very tedious task.

This script is written in PowerCLI, VMware KB shows more ways of detaching disks.

Script initialization (Environment names, Snapin, log location, time function):

$vCenterName = "vcenter.company.corp"  # ChangeME
$ClusterNameToDetach = "SQL-Cluster-A" # ChangeME

if (!(get-pssnapin -name VMware.VimAutomation.Core -erroraction silentlycontinue)) {
    add-pssnapin VMware.VimAutomation.Core}

Connect-VIServer $vCenterName

cd F:\Scripts\Serv_DetachClusterDevices\
$dslog_file = ".\log\"+$ClusterNameToDetach+"_log.txt" 

Function Givemetime(){
$mydate = Get-Date
return $mydate = $mydate.ToShortDateString() +" "+ $mydate.ToLongTimeString() +" "
}

Adding some of the detach disk functions:

function Detach-Disk{
    param(
        [VMware.VimAutomation.ViCore.Impl.V1.Inventory.VMHostImpl]$VMHost,
        [string]$CanonicalName
    )
    
    $storSys = Get-View $VMHost.Extensiondata.ConfigManager.StorageSystem
    $lunUuid = (Get-ScsiLun -VmHost $VMHost | 
      where {$_.CanonicalName -eq $CanonicalName}).ExtensionData.Uuid
    
    $storSys.DetachScsiLun($lunUuid)
}

Function Detach-RDMs ($naa){
Foreach ($vmhost in $vmhosts){
Write-Host "Starting with"$vmhost.Name
"$(Givemetime)" + "Starting with " +$vmhost.Name | out-file $dslog_file -append -Force
if (($vmhost |  Get-ScsiLun| Where {($_).ExtensionData.OperationalState -eq "ok"} | %{$_.CanonicalName}) -contains ($naa) ){

Write-Host "Detaching device"$naa
"$(Givemetime)" + "Detaching device "+$naa | out-file $dslog_file -append -Force
Detach-Disk -VMHost $vmhost -CanonicalName $naa
Write-Host "Done"
"$(Givemetime)" + "Done" | out-file $dslog_file -append -Force
}
}}

The power of the script comes here – the ability to map the devices in the cluster, and separate them to few groups:
$ClusterDSDevices – Devices that are used as datastores in the cluster
$ClusterRDMDevices – Devices that are used as RDMs in the cluster
$ClusterVMAXDevices – All Devices that are visible by the cluster, from a specific vendor. In this example, EMC VMAX devices will presented
$AllUseddevices – Both $ClusterDSDevices and $ClusterRDMDevices variables combined

Variables will contain a list of naa devices, and it is your job to determine what should be declared for detach by the end of the script.
In the end of the mapping script I added an example I used – filter for:All VMAX devices (both DS and RDM), that are not in use. It will list the required devices in $DevicesForDetach variable.

function MapDevices (){
$global:DevicesForDetach = $null
$global:vmhosts = Get-Cluster $ClusterNameToDetach | Get-VMHost | Sort Name

$global:ClusterDSDevices = $null
$global:ClusterDSDevices =  Get-VMHost $vmhosts | Get-Datastore | Where-Object {$_.ExtensionData.Info.GetType().Name -eq "VmfsDatastoreInfo"} | %{$_.ExtensionData.info.Vmfs.Extent[0].DiskName}

$global:ClusterRDMDevices = $null
$global:ClusterRDMDevices = Get-VMHost $vmhosts | Get-VM | Get-HardDisk -DiskType "RawPhysical","RawVirtual" | %{$_.ScsiCanonicalName}

$global:ClusterVMAXDevices = $null
$global:ClusterVMAXDevices = Get-VMHost $vmhosts | Get-ScsiLun | Where {($_).ExtensionData.OperationalState -eq "ok"} | %{$_.CanonicalName} | Where {$_ -like "naa.600009*"} | select -Unique

$global:AllUseddevices = $ClusterDSDevices + $ClusterRDMDevices
$global:AllUseddevices = $AllUseddevices | Where {$_ -like "naa.600009*"} | select -Unique

$global:AllVMAXDevices = $ClusterVMAXDevices

## Change it to your reference:
If ($AllUseddevices){
$global:DevicesForDetach = (Compare-Object -DifferenceObject $AllVMAXDevices -ReferenceObject $AllUseddevices -IncludeEqual | Where {$_.SideIndicator -eq "=>"}) | %{$_.InputObject}
}
Else {$global:DevicesForDetach = $global:AllVMAXDevices}
## End of change
}

It is now time to use the list we created, ans start detaching disks, all tasks will be exported to a log.

Write-Host "Device Mapping started..."
"$(Givemetime)" + "Device Mapping started..." | out-file $dslog_file -append -Force
MapDevices
Write-Host "Device Mapping finished"
"$(Givemetime)" + "Device Mapping finished" | out-file $dslog_file -append -Force
if (!($DevicesForDetach)) {
Write-Host "No devices to detach"
"$(Givemetime)" + "No devices to detach on cluster" | out-file $dslog_file -append -Force
}
else {
Foreach ($device in $DevicesForDetach){
Detach-RDMs ($device)
}
}

Log file will look like that in the end of the process:

log-detach2

In this example, 19 devices were detached from 9 hosts, resulted in 171 detach commands to the ESXi hosts in the cluster. The script took less than an hour to complete it.

ESXi 5.x – VM reset stuck at 95%

Few weeks ago we had an issue with freezing VM, and the only way of getting out of it was to perform hard reset to the VM. The procedure is well known – right click on the VM > Power > Reset.

tasks

The command took quite a while to actually initiate the reset. I really wondered what happened there, so I started investigating it.

vmkernel log didn’t show anything. The VM’s vmware.log log on the other hand, was quite interesting:


2014-09-25T03:44:35.209Z| vmx| VMMon_VSCSIStopVports: Invalid handle
2014-09-25T03:44:35.209Z| vmx| VMMon_VSCSIDestroyDev: Not found
2014-09-25T04:13:15.305Z| vmx| ide1:0: Command TEST UNIT READY took 2264.916 seconds (ok)
2014-09-25T04:13:15.305Z| vmx| SOCKET 9 (98) disconnecting VNC backend by request of remote manager
2014-09-25T04:13:15.307Z| vmx| MKS local poweroff

It looked like TEST UNIT READY took 37min to complete! Only after this command finished, the VM reset preparation continued. So what is this mysterious command, and what took it so long to complete?
TEST UNIT READY is a SCSI command sent to the target in order to get a response, and a status of the device. In the log file, ide1:0 is referred, but CD-ROM drive in this VM isn’t mounted according to vSphere client –

Edit-Settings

Just to make sure, I checked out the vmx file, which showed me something I didn’t see from the vSphere client side –

vmx-editor

vmx file indicates of an iso mounted to this VM. Not just an iso, but the VMware tools iso. Checking again from the guest OS side – and the disk isn’t mounted. So what just happened here?

A bug.

http://kb.vmware.com/kb/2076512

This KB has a low rating of 2 stars right now, probably since the solution didn’t include PowerCLI, right? Here it is.

Mapping of the VMs with ghosted CD-ROM:

Get-VM | Get-CDDrive | Where {$_.ExtensionData.Backing.DeviceName -like "*iso"} |  Select Parent,
@{N="DeviceName"; E={($_).ExtensionData.Backing.DeviceName}} | Export-Csv F:\Scripts\Temp\CD-Drive-mapping.csv

The actual removal of the CD-ROM from all VMs:

Get-VM | Get-CDDrive | Where {$_.ExtensionData.Backing.DeviceName -like "*iso"} | Set-CDDrive -NoMedia -Confirm:$false

Works best on 64Bit PowerShell console.

Enjoy.

iLO inventory in Multi-Domain ESXi environment

On enterprise companies, you might find a vCenter managing few DNS zones (a.serv.corp, b.serv.corp etc.). There might be a situation where there are few vCenters, each manages a different domain. Keeping track on HP iLO* IPs is easier when you register the iLO IP in the DNS, and use “ilo” as a prefix to each server – for example, esx01 iLO address will be ilo-esx01 and so on. It all works until it doesn’t, since host name changes, and maintaining it for more than just a few ESXi hosts can be demanding. Enters PowerShell.

I’ll assume you have some PowerShell / PowerCLI experience, and you know how to add the right snapin, and connect to the relevant vCenter(s).

First, add the Get-VMHostWSManInstance function, with prerequisites described here:

http://blogs.vmware.com/PowerCLI/2009/03/monitoring-esx-hardware-with-powershell.html

 function Get-VMHostWSManInstance {
            param (
            [Parameter(Mandatory=$TRUE,HelpMessage="VMHosts to probe")]
            [VMware.VimAutomation.Client20.VMHostImpl[]]
            $VMHost,
     
            [Parameter(Mandatory=$TRUE,HelpMessage="Class Name")]
            [string]
            $class,
     
            [switch]
            $ignoreCertFailures,
     
            [System.Management.Automation.PSCredential]
            $credential=$null
            )
     
            $omcBase = "http://schema.omc-project.org/wbem/wscim/1/cim-schema/2/"
            $dmtfBase = "http://schemas.dmtf.org/wbem/wscim/1/cim-schema/2/"
            $vmwareBase = "http://schemas.vmware.com/wbem/wscim/1/cim-schema/2/"
     
            if ($ignoreCertFailures) {
                    $option = New-WSManSessionOption -SkipCACheck -SkipCNCheck -SkipRevocationCheck
            } else {
                    $option = New-WSManSessionOption
            }
            foreach ($H in $VMHost) {
                    if ($credential -eq $null) {
                            $hView = $H | Get-View -property Value
                            $ticket = $hView.AcquireCimServicesTicket()
                            $password = convertto-securestring $ticket.SessionId -asplaintext -force
                            $credential = new-object -typename System.Management.Automation.PSCredential -argumentlist $ticket.SessionId, $password
                    }
                    $uri = "https`://" + $h.Name + "/wsman"
                    if ($class -cmatch "^CIM") {
                            $baseUrl = $dmtfBase
                    } elseif ($class -cmatch "^OMC") {
                            $baseUrl = $omcBase
                    } elseif ($class -cmatch "^VMware") {
                            $baseUrl = $vmwareBase
                    } else {
                            throw "Unrecognized class"
                    }
                    Get-WSManInstance -Authentication basic -ConnectionURI $uri -Credential $credential -Enumerate -Port 443 -UseSSL -SessionOption $option -ResourceURI "$baseUrl/$class"
            }
    }

 

Using System.Net.DNS .Net class isn’t enough for us, since we want to query few domain DNS servers.

You’ll need the DNSShell module installed in order to reverse lookup in each of the domains:

http://dnsshell.codeplex.com

Import-Module DnsShell

Please note that this module might not work well on 32Bit PowerShell version, so use 64Bit PowerShell console.

From this point, the script itself is straight forward, gathers all the data required including: vCenter Name, VMHost Name, Domain, iLO-IP, iLO-DNS.

Get-VMHost | Select `
@{n="vCenter"; e={($_.uid.split("@")[1]).split(":")[0] }},
@{n="VMHostName"; e={$_.Name}},
@{n="Domain"; e={$script:domain = ($_ | Get-VMHostNetwork).DomainName; $domain}},
@{n="iLOIP"; e={$script:ip = (Get-VMHostWSManInstance -VMHost ($_) -ignoreCertFailures -class OMC_IPMIIPProtocolEndpoint).IPv4Address; $ip}},
@{n="iLODNS"; e={((Get-Dns -Server $domain -Tcp $ip).Answer | Select -ExpandProperty RecordData) -join ","}} | Export-Csv -Path F:\Scripts\Output\iLO-inventory.csv F -NoTypeInformation

 

Output will look like:

excel-output-ilo

 

* HP iLO is referenced here, script will work for any other IPMI technology such as Cisco CIMC, Dell DRAC, etc.