testing321
Contributor
Contributor

Getting all USB devices connected to VCenter

Hello,

currently I have a problem, which involves vSphere/VCenter. I have a VCenter with many USB devices connected to different ESXi and I have to connect an USB device to a specific VM on demand. Now I need to know the USB path to these devices. The one that looks like this

deviceName = "path:1/0/3/3/2/5/1 version:2"

I can connect to the ESXi and get this path for device that are already connected to VMs with this command. The second command gives me the ID of the VM, which replaces XXXX in the first command

vim-cmd vmsvc/device.getdevices XXXX | grep path

vim-cmd vmsvc/getallvms

Now the problem is that I do not get the paths for USB devices, that are not connected to a VM. I know that I can connect a device with the device ID, that i get with lsusb, but the problem here is that all my device have the same VendorID and ProductID, because there are the same product and from the same vendor. So the vSphere Client can differentiate between all USB devices and knows which are connected to which ESXi etc. But I did not find any way to get this information via vSphere Web API or some ESXi vim-cmd.

Are there any ways to get this information?

I am using vSphere 5.5

Kind regards

Update:

So i found a solution for this

I used python for this and you need these two libs for it pyVim and pyVmomi. These are used to communicate with the vSphere Server and everything

Here is the code

from pyVim import connect

from pyVmomi import vmodl

from pyVmomi import vim

service_instance = connect.SmartConnect(host=<Ip of your vSphere server>,

                                            user=<userName>,

                                            pwd=<password>,

                                            port=<your port>)

   

    si = service_instance.content.searchIndex

    atexit.register(connect.Disconnect, si)

    # get your datacenter

     content = service_instance.RetrieveContent()

    dc = None

    for datacenter in content.rootFolder.childEntity:

        if datacenter.name != <name of datacenter>:

            continue

        dc = datacenter

        break

       

    if dc is None:

        print("did not find the datacenter")

        return 0

   

    viewType = [vim.HostSystem]  # object types to look for

    recursive = True  # whether we should look into it recursively

    

    containerView = content.viewManager.CreateContainerView(dc, viewType, recursive)

    children = containerView.view

    containerView.Destroy()

    # get the compute resource and vm list

    container = content.viewManager.CreateContainerView(dc, [vim.ComputeResource], True)

    containerVM = content.viewManager.CreateContainerView(dc, [vim.VirtualMachine], True)

   

    vmList = []

    for vm in containerVM.view:

        vmList.append(vm)

    containerVM.Destroy()

   

     # get my current agent/VM on which i am running

    IP = socket.gethostbyname(socket.gethostname())

    myself = None

    myself = si.FindByIp(None, IP, True)

    hosts = {}

    devices = []

    keysAlreadyConnected = {}

   

    print("--------------------")

    print("searching for all USB devices available in the datacenter")

    print("--------------------")

    with open("log", 'w') as f:

        for child in children:

            if len(child.vm) < 1:

                print("no vms, so no key checking for {0}".format(child.name))

                continue

            if <specific esxi name> not in child.name:

                continue

            f.write("host: {0}".format(child.name))

            hosts[child.name] = child

           

            # get a compute resource to get the environmentBrowser to be able to run QueryConfigTarget

            obj = None

            for c in container.view:

                if c.name == child.parent.name:

                    obj = c

                    break

            if obj is None:

                print("no compute resource found")

                continue

            envBrowser = obj.environmentBrowser

            if envBrowser is None:

                print("no env browser")

                return 0

            result = envBrowser.QueryConfigTarget(child)

            # end of workaround

           

           

            if result is not None:

                f.write("no usb devices: {0}\n".format(len(result.usb)))

                for usbDevice in result.usb:

                    deviceName = usbDevice.description

                    f.write("\n\n" + usbDevice.description + "\n")

                    f.write(usbDevice.physicalPath+ "\n")

                    f.write(usbDevice.name+ "\n")

                    f.write(str(usbDevice.configurationTag)+ "\n")

                    if <my specific device name> in deviceName:

                         # this only works for VMs that are turned on

                        if usbDevice.summary is not None:

                            connectedVM = usbDevice.summary

                            if connectedVM is not None:

                                f.write("connected to {0}\n".format(connectedVM.config.name))

                                vmName = connectedVM.config.name

                                # this is only for debugging and is for using only specific agents/VMs

                                if <specific VM name>  in vmName:

                                    devices.append(usbDevice.physicalPath)

                                    print("will check {0} from vm {1}".format(deviceName, vmName))

                        else:

                            devices.append(usbDevice.physicalPath)

                            print(usbDevice.summary)

                            print("will check {0} that is not connected".format(deviceName))

          # check vms to look for connected usb devices on powered off VMs

             for vm1 in vmList:

                 if vm1.name == myself.name:

                     continue

                 for device in vm1.config.hardware.device:

                     if not isinstance(device, vim.vm.device.VirtualUSB):

                    continue

                if device.backing.deviceName in devices:

                    keysAlreadyConnected[device.backing.deviceName] = vm1

               

            else:

                print ("did not find {0}".format(uuid))

This works for me. I think these Python libs are also available for Java and this solution should work there too.

The indention is probably messed up but I hope this will help everyone with the same problem.

Here is the code to add or remove a usb device

def addOrRemoveAllDevicesFromCurrentAgent(vm, deviceList, remove = False):

   

    vm_spec = vim.vm.ConfigSpec()

    usb_changes = []

    for device in deviceList:

       

        usb_spec = vim.vm.device.VirtualDeviceSpec()

        if remove:

            usb_spec.operation = vim.vm.device.VirtualDeviceSpec.Operation.remove

        else:

            usb_spec.operation = vim.vm.device.VirtualDeviceSpec.Operation.add

        usb_spec.device = device

        usb_changes.append(usb_spec)

    vm_spec.deviceChange = usb_changes

    e = vm.ReconfigVM_Task(spec=vm_spec)

    x = 0

    while not e.info.completeTime and x < 30:

        time.sleep(1)

        x = x + 1

This should be understandable and is pretty straight forward.

0 Kudos
5 Replies
Disbalance
Contributor
Contributor

Hi, @testing321

I have the same case, i have 3 ESXI hosts and per 1 VM on them. Also i have USB devices connected to every ESXI host and i need to passthrough them to every VM.

I tried to run your script but it didn’t work me. Since I’m new with python i can’t understand what problem it have.

Could you help me with this ?

0 Kudos
testing321
Contributor
Contributor

Hi,

what is your problem? Are all your ESXi in the same datacenter and cluster?

0 Kudos
Disbalance
Contributor
Contributor

Hi, testing321

Yes, all of my ESXI hosts located in the same datacenter and cluster.

Every host have connected USB devices which should be passed through to VM. Also every  ESXI host have only 1 VM per host.

My problem that i don’t know how i can do this. I tried to run you script, but it didn’t work. As I’m new with python it’s hard for me to understand where problem in script is.

0 Kudos
testing321
Contributor
Contributor

Ok. I try to explain the different parts of the script. Hopefully this will help you.

Here you connect to your VCenter/vSphere Server.

  1. service_instance = connect.SmartConnect(host=<Ip of your vSphere server>, 
  2.                                             user=<userName>, 
  3.                                             pwd=<password>, 
  4.                                             port=<your port>) 
  5.  
  6.      
  7.     si = service_instance.content.searchIndex 
  8.     atexit.register(connect.Disconnect, si) 
  9.  
  10.     # get your datacenter 
  11.      content = service_instance.RetrieveContent() 

Now you try to get the right datacenter. If you have multiple you iterate through all and look for the right one via the name. It seems you only have 1 and you can just pick the first item in the list.

  1.     dc = None 
  2.     for datacenter in content.rootFolder.childEntity: 
  3.         if datacenter.name != <name of datacenter>: 
  4.             continue 
  5.         dc = datacenter 
  6.         break 
  7. # just some error checking         
  8.     if dc is None
  9.         print("did not find the datacenter"
  10.         return 0 
  11.     

Here i get all available ESXi in the datacenter. The CreateContainerView function does everything in this case.

  1.     viewType = [vim.HostSystem]  # object types to look for 
  2.     recursive = True  # whether we should look into it recursively    
  3.     containerView = content.viewManager.CreateContainerView(dc, viewType, recursive) 
  4.     children = containerView.view 
  5.     containerView.Destroy()

  Now I get all VMs available in the datacenter and create a list of them. Again this works with the CreateContainerView function. I destroy the container I do not need anymore after i used them.

  1.     # get the compute resource and vm list 
  2.     container = content.viewManager.CreateContainerView(dc, [vim.ComputeResource], True
  3.     containerVM = content.viewManager.CreateContainerView(dc, [vim.VirtualMachine], True
  4.      
  5.     vmList = [] 
  6.     for vm in containerVM.view: 
  7.         vmList.append(vm) 
  8.     containerVM.Destroy() 

If you running the script on a VM which is running on VCenter, this is getting the VM object of this agent. Myself will be the VM the script is running on. This only works if you use it on a VM that runs in the VCenter/Datacenter you are checking your USB devices.

  1.    # get my current agent/VM on which i am running 
  2.     IP = socket.gethostbyname(socket.gethostname()) 
  3.     myself = None 
  4.     myself = si.FindByIp(None, IP, True
  5.     hosts = {} 
  6.     devices = [] 
  7.     keysAlreadyConnected = {} 
  8.      
  9.     print("--------------------"
  10.     print("searching for all USB devices available in the datacenter"
  11.     print("--------------------"

This is for writing a log file. You can replace all f.write() with print() calls to get the ouput directly

  1.     with open("log", 'w') as f: 

Now iterate through all Hosts/ESXi. I start with some checks if the ESXi has VMs and if it is a specific ESXi I am looking for. Then I create a list of ESXi/Host objects.

  1.         for child in children: 
  2.             if len(child.vm) < 1
  3.                 print("no vms, so no key checking for {0}".format(child.name)) 
  4.                 continue 
  5.             if <specific esxi name> not in child.name: 
  6.                 continue 
  7.             f.write("host: {0}".format(child.name)) 
  8.             hosts[child.name] = child 
  9.              

Here I gete the environmentBrowser through a ComputeResource object. The parent of the ESXi object has to be the ComputeResource. I am not sure why it was that way. I just found this code online and it works for me. I think if you want to know more you have to check the vSphere API documentation.The environmentBrowser is used for the QueryConfigTarget() function.

  1.             # get a compute resource to get the environmentBrowser to be able to run QueryConfigTarget 
  2.             obj = None 
  3.             for c in container.view: 
  4.                 if c.name == child.parent.name: 
  5.                     obj = c 
  6.                     break 
  7.             if obj is None
  8.                 print("no compute resource found"
  9.                 continue  
  10.             envBrowser = obj.environmentBrowser 
  11.             if envBrowser is None
  12.                 print("no env browser"
  13.                 return 0 
  14.             result = envBrowser.QueryConfigTarget(child) 
  15.             # end of workaround 
  16.              
  17.   

The result of QueryConfigTarget() is the complete configuration of the ESXi. I only take the list of USB device and check them.          

  1.             if result is not None
  2.                 f.write("no usb devices: {0}\n".format(len(result.usb))) 
  3.                 for usbDevice in result.usb: 
  4. # just some log output
  5.                     deviceName = usbDevice.description 
  6.                     f.write("\n\n" + usbDevice.description + "\n"
  7.                     f.write(usbDevice.physicalPath+ "\n"
  8.                     f.write(usbDevice.name+ "\n"
  9.                     f.write(str(usbDevice.configurationTag)+ "\n"

Here I start checking the USB devices. I check for a specific name of a device and then check if it is connected to a VM. If the VM is running that the USB device it connected to, the USB device knows the VM - see usbDevice.summary -. If the VM is turned off the USB device does not know it. I create a list of USB devices.

  1.                     if <my specific device name> in deviceName: 
  2.                          # this only works for VMs that are turned on 
  3.                         if usbDevice.summary is not None
  4.                             connectedVM = usbDevice.summary 
  5.                             if connectedVM is not None
  6.                                 f.write("connected to {0}\n".format(connectedVM.config.name)) 
  7.                                 vmName = connectedVM.config.name 
  8.                                 # this is only for debugging and is for using only specific agents/VMs 
  9.                                 if <specific VM name>  in vmName: 
  10.                                     devices.append(usbDevice.physicalPath) 
  11.                                     print("will check {0} from vm {1}".format(deviceName, vmName)) 
  12.                         else
  13.                             devices.append(usbDevice.physicalPath) 
  14.                             print(usbDevice.summary) 
  15.                             print("will check {0} that is not connected".format(deviceName)) 

Here I create a map of USB devices that are connected to other VMs

  1.           # check vms to look for connected usb devices on powered off VMs 
  2.              for vm1 in vmList: 
  3.                  if vm1.name == myself.name: 
  4.                      continue 
  5.                  for device in vm1.config.hardware.device: 
  6.                      if not isinstance(device, vim.vm.device.VirtualUSB): 
  7.                     continue 
  8.                 if device.backing.deviceName in devices: 
  9.                     keysAlreadyConnected[device.backing.deviceName] = vm1 
  10.                  
  11.             else
  12.                 print ("did not find {0}".format(uuid)) 

At the end of this you should have a list and a map of your USB devices. Some parts are specific in my case because we have multiple Cluster with multiple ESXi. This is a limitation too. The ESXi must be in the same cluster. I know there is a setting to allow sharing of devices over multiple clusters, but I do not know if the script works with that.

Here is some code to add or remove a USB device to a VM. Before that you always have to login to the VCenter/vSphere server as seen at the top.

After logging in you can find the VM you want via the name like this:

# dc is the Datacenter object

    clusterList = content.viewManager.CreateContainerView(

        dc, [vim.ComputeResource], True)

   

    agent = None

    # find the VM

    # there is no function to find it by the name provided by the API

    for cluster in clusterList.view:

        clusterListVM = content.viewManager.CreateContainerView(

            cluster, [vim.VirtualMachine], True)

        vmList = clusterListVM.view

        clusterListVM.Destroy()

# finding the VM via the name

        for vm in vmList:

            if agentName == vm.name:

                agent = vm

                break

        if agent is not None:

            break

       

    if agent is not None:

        print("adding the key")

        addOrRemoveKey(agent, keyPath)

        print("added {0} to agent {1}".format(keyPath, agent.name))

----------------------------------------------------------------------------------------------------------

Here is the code for adding or removing a device:

def addOrRemoveKey(vm, device, remove=False):

    """

    add or remove a single key to the specified agent

    """

    usb_changes = []

    vm_spec = vim.vm.ConfigSpec()

    usb_spec = vim.vm.device.VirtualDeviceSpec()

    # to not use keys that are blacklisted

    if not checkKeyBlackListState(device, vm.summary.runtime.host.name):

        print("avoid blacklisted key")

        return False

    if remove:

        usb_spec.operation = vim.vm.device.VirtualDeviceSpec.Operation.remove

    else:

        usb_spec.operation = vim.vm.device.VirtualDeviceSpec.Operation.add

# check if the device is provided as a VirtualUSB object or as a string

# the string would be the usb path from the  vSphere server. It looks like this:

# path: 1/2/3/4... verison: 2

    if str == type(device):

        usb_spec.device = vim.vm.device.VirtualUSB()

        usb_spec.device.backing = vim.vm.device.VirtualUSB.USBBackingInfo()

        usb_spec.device.backing.deviceName = device

    else:

        usb_spec.device = device

    usb_changes.append(usb_spec)

# here the device gets attached or removed

    vm_spec.deviceChange = usb_changes

    e = vm.ReconfigVM_Task(spec=vm_spec)

# wait until the task is done

    while e.info.state == vim.TaskInfo.State.running:

        time.sleep(1)

# some error "handling"

    if e.info.state == vim.TaskInfo.State.error:

        msg = "Error adding {0} to {1}"

        if remove == True:

            msg = "Error removing {0} from {1}"

        if str == type(device):

            print(msg.format(device, vm.name))

        else:

            print(msg.format(device.backing.deviceName, vm.name))

        print("--------------------------")

        print("VCenter error message")

        print(e.info.error)

        print("--------------------------")

        return False

    return True

0 Kudos
Disbalance
Contributor
Contributor

That worked for me

Thank You for helping and for the detailed explanation.

0 Kudos