1

I'm looking into moving an existing powercli deployment script to python/pyvmomi, to get multi-threading (it deploys a LOT of VMs). The original script makes fairly heavy use of Invoke-VMScript to push powershell fragments to each guest via the VMware Tools.

What's the equivalent functionality in pyvmomi? Specifically - send a powershell script to the guest via Tools (not the guest's network), have it run with supplied credentials, and then collect the output?

I can see processManager.StartProgramInGuest, but that seems like a clunky 3-step process (upload file, run file, download redirected results) - is that what powercli is doing in the background?

AnotherHowie
  • 818
  • 1
  • 11
  • 28

1 Answers1

3

So in the interests of providing some closure, and because I couldn't find a complete example anyway, here's my first take at this. It's part of a class that has already connected to the vcenter server using SmartConnect, and set self.si. It doesn't really do much error checking yet. You can choose if you want to wait and get the output, or just return after launching the command. remote_cmd originally came from pyvmomi-community-samples, hence some duplication between the two methods currently.

def invoke_vmscript(self, vm_username, vm_password, vm_name, script_content, wait_for_output=False):

    script_content_crlf = script_content.replace('\n', '\r\n')

    content = self.si.content
    creds = vim.vm.guest.NamePasswordAuthentication(username=vm_username, password=vm_password)
    vm = self.get_vm(vm_name)
    logger.debug("Invoke-VMScript Started for %s", vm_name)
    logger.debug("CREATING TEMP OUTPUT DIR")
    file_manager = content.guestOperationsManager.fileManager

    temp_dir = file_manager.CreateTemporaryDirectoryInGuest(vm, creds, "nodebldr_",
                                                            "_scripts")
    try:
        file_manager.MakeDirectoryInGuest(vm, creds, temp_dir, False)
    except vim.fault.FileAlreadyExists:
        pass
    temp_script_file = file_manager.CreateTemporaryFileInGuest(vm, creds, "nodebldr_",
                                                               "_script.ps1",
                                                               temp_dir)
    temp_output_file = file_manager.CreateTemporaryFileInGuest(vm, creds, "nodebldr_",
                                                               "_output.txt",
                                                               temp_dir)
    logger.debug("SCRIPT FILE: " + temp_script_file)
    logger.debug("OUTPUT FILE: " + temp_output_file)
    file_attribute = vim.vm.guest.FileManager.FileAttributes()
    url = file_manager.InitiateFileTransferToGuest(vm, creds, temp_script_file,
                                                   file_attribute,
                                                   len(script_content_crlf), True)
    logger.debug("UPLOAD SCRIPT TO: " + url)
    r = requests.put(url, data=script_content_crlf, verify=False)
    if not r.status_code == 200:
        logger.debug("Error while uploading file")
    else:
        logger.debug("Successfully uploaded file")

    self.remote_cmd(vm_name, vm_username, vm_password, 'C:\\WINDOWS\\system32\\WindowsPowerShell\\v1.0\\powershell.exe',
                    "-Noninteractive {0} > {1}".format(temp_script_file, temp_output_file), temp_dir,
                    wait_for_end=wait_for_output)

    output = None
    if wait_for_output:
        dl_url = file_manager.InitiateFileTransferFromGuest(vm, creds,
                                                            temp_output_file)
        logger.debug("DOWNLOAD OUTPUT FROM: " + dl_url.url)
        r = requests.get(dl_url.url, verify=False)
        output = r.text
        logger.debug("Script Output was: %s", output)

    logger.debug("DELETING temp files & directory")
    file_manager.DeleteFileInGuest(vm, creds, temp_script_file)
    file_manager.DeleteFileInGuest(vm, creds, temp_output_file)
    file_manager.DeleteDirectoryInGuest(vm, creds, temp_dir, True)
    logger.debug("Invoke-VMScript COMPLETE")

    return output

def remote_cmd(self, vm_name, vm_username, vm_password, command, args, working_dir, wait_for_end=False, timeout=60):
    creds = vim.vm.guest.NamePasswordAuthentication(username=vm_username, password=vm_password)
    vm = self.get_vm(vm_name)
    try:
        cmdspec = vim.vm.guest.ProcessManager.ProgramSpec(arguments=args, programPath=command)
        pid = self.si.content.guestOperationsManager.processManager.StartProgramInGuest(vm=vm, auth=creds,
                                                                                        spec=cmdspec)
        logger.debug("Started process %d on %s", pid, vm_name)
    except vmodl.MethodFault as error:
        print("Caught vmodl fault : ", error.msg)
        return -1

    n = timeout
    if wait_for_end:
        while n > 0:
            info = self.si.content.guestOperationsManager.processManager.ListProcessesInGuest(vm=vm, auth=creds,
                                                                                              pids=[pid])
            if info[0].endTime is not None:
                break
            logger.debug("Process not yet completed. Will wait %d seconds", n)
            sleep(1)
            n = n - 1
        logger.debug("Process completed with state: %s", info[0])
AnotherHowie
  • 818
  • 1
  • 11
  • 28
  • Although I can create a directory on the virtual machine, the response.put always gives me a NewConnectionError, "Connection timed out". I'm trying to reach a Windows Server 2012 virtual machine. I get the error even when I create a rule allowing connections on the port being used. – HeywoodFloyd Aug 09 '19 at 15:43
  • The connection isn't to the Windows server, it's to the ESX host. That's the whole point of this - you can push files into a VM environment even if you don't have any network access to that env from your management station. – AnotherHowie Aug 09 '19 at 21:47
  • Print the url, and you'll see exactly where it's going! – AnotherHowie Aug 09 '19 at 21:53