About Async tasks, the Get-Task cmdlet and a hash table

There was an interesting question in the PowerCLI Community on how to use the -RunAsync parameter. The user wanted to create a number of new guests and start each guest once the creation was complete. This can be done rather easily by using a New-VM cmdlet and piping the result to the Start-VM cmdlet.

Only problem was, the creation of the new guests was done in Async mode.

That’s where the Get-Task cmdlet and the use of a hash table come in handy.

With the Get-Task cmdlet you can get the status of tasks running on a vSphere server.

And a hash table has several methods which come in very handy for the task at hand.

The script


Line 1: Define the name of the vSphere server on which the new guests will be created

Line 2: The “model” VM from which the new guests will be cloned

Line 3: An array specifying the names of the new guests

Line 4: Define the variable $taskTab as a hash table

Line 7-9: Create the new guests. The New-VM cmdlet is executed with the -RunAsync parameter which means the script continues once the “Clone virtual machine” task is started.

Line 8: The New-VM cmdlet, when run with the -RunAsync parameter, will produce a TaskImpl object. The Id property in this TaskImpl object is used as the “key” into the hash table and for the “value” the name of the new guest is used.

Line 12: Once the creation of all new guests is done, the script determines how many tasks were started by asking the Count property of the hash table.

Line 13: While there are tasks left in the hash table the loop will execute.

Line 14: With the Get-Task cmdlet the script gets the status of all recent tasks. And then loops through all the returned tasks.

Line 15: When the Task-Id exists as a key in the hash table, the script checks if the task was completed successfully.

Line 16: If so, the new guest is started. The -Name parameter is obtained by using the Task-Id as the “key” into the hash table which returns the “value“.

Line 17: The task, that was completed, is then removed from the hash table with the Remove method

Line 18: And the number of running tasks is decremented with one

Line 20: A second test checks if the task is one of those created by the New-VM cmdlet and if the Status of the task is “error“.

Line 21: If so, the Task-Id entry is removed from the hash table

Line 22: And the number of tasks is decremented by one.

Line 25: At this point the script will “sleep” for 15 seconds.


As you have seen it is quite useful to know how to use a hash table. This special type of array brings you a lot of handy methods that you can use in your script’s logic. It avoids that your script becomes unnecessary complex.

And as you also might have noticed, the Get-Task cmdlet is your friend when you run cmdlets in the Async mode.




    if i use

    $objTask = New-VM -Name $hostname -ResourcePool $objCluster -ContentLibraryItem $objContentLibrary -Location $objFolder -Datastore $objDatastoreCluster -RunAsync
    write-host \”Deploy OVF Template: Starting Job…\”
    Wait-Task $objTask

    After some time the error is returned:

    Wait-Task : 19/06/2017 14:05:49 Wait-Task 19/06/2017 13:54:45 Wait-Task The operation has timed out
    At line:1 char:1
    + Wait-Task $objTask
    + ~~~~~~~~~~~~~~~~~~
    + CategoryInfo : NotSpecified: (:) [Wait-Task], ViError
    + FullyQualifiedErrorId : VICore_VMServiceImpl_DeployFromLibraryItem_Error,VMware.VimAutomation.ViCore.Cmdlets.Commands.WaitTask

    Even giving this error I realized that in vcenter there are still two tasks: Deploy OVF package from Content Library and then the Deploy OVF template
    This two tasks are executed normamelmente and vm was created even with the timeout error.
    Note: The template has 25GB


    I’ve been working on creating a script that create a large number of VMs at a time (>100) asynchronously using your method in the post. My question is how many can/should be created at a time? It seems like this should be limited rather than having all 100+ VMs being created at once. Any best practices or guidelines here? Thanks.


      Hi Bruce,
      There are some builtin limits in vSphere.
      From the same template you can’t create more than 8 VMs in parallel for example.
      But I normally limit the number of parallel background tasks to create new VMs to 4.

      It depends of course on your environment.

      I’m not sure if there is a Best Practices Guide on this.


    Hi Luc! I know this is quite old post, also it is for powercli 4.x, but still…
    I’m trying to introduce (some kind of) control over async tasks in “my” batch vm deployment script, but I’m really in the pits.
    It seems like in powercli 5.x there are two separate types for “client-side” tasks and “server-side” tasks (VMware.VimAutomation.Sdk.Util10.Task.ClientSideTaskImpl and VMware.VimAutomation.ViCore.Impl.V1.Task.TaskImpl respectively).
    For each of these types .id and .uid properties look completely different (I’ve got a nice screenshot somewhere to show you what I mean 😉 ), so it is impossible to match (and track) these tasks by .id like you show it here.
    I was trying to find some other property that would enable such matching (in easy way) but wasn’t successful.
    Then I tried to track only “client-side” tasks, but when I gather them in hash-table or array-list and then query the tasks for state it seems like their states are not updated (even when deployments are finished in vCenter, all the tasks show “Running” state in array/table, so the script never gets to “power-on” part).
    I would like to avoid searching through vCenter events just to check if “new-vm” task has finished (not even sure if this is possible without valid virtual machine object).
    Hope I’m not asking for anything that can be easily googled and I’d be most grateful for any hints.


      OK, disregard my question, found the solution…
      PEBKAC of course 😉


        No problem Sebastian.
        You learn more from a solution you found yourself, then from one you copied 🙂


          Well, I’m basically still using “your method” (kudos!), I just made some stupid mistake when first implementing it and this has lead me “all around the world” to come back to what you described here… Still a learning experience, I fully agree 😉
          Thanks for your reply!


        What was the solution? I’m new to powershell and it would be helpful to see it.


    Hi Luc;
    i know this is very old post.. 2 years now, still want to appreciate for this info i have a question on this-
    how many task we can run from this.. there will be limit for get-task right?

    in my place we have to deploy more thn 100 nodes at a time and so many concurrent tasks will run by other colleagues on same vCenter.. for you this may be a very hypothetical situation but for such case this method fails.

    if $taskTab.ContainsKey($_.Id) is not cumming in get task subsequent action doesn’t get performed.
    in addition i also want to know about $_state… (i found Get-Task has parameters Status and Server) is state and Status are same..

    is there any way to filter Get-task by its name on top (like CloneVM_Task or Migration_Task)
    like this
    Get-task -Name CloneVM_Task | %{ blah bla bla } // i know name parameter is not there (only status is what we can filter; still i just want if you can provide a workaround)

    I just have started my journey with VMware and automation so these questions might be silly..



    What is the name of the text editor that contains line number and green vertical line?


      @Martin, that is done by a WordPress plugin called SyntaxHighlighter Evolved.


    LucD!!! Quick Question….with the $tasktab it looks like you are immediately adding the task id’s from the new-vm -runasync to the $tasktab array…Is that right??? I don’t think I have seen that before. What is that way of adding values to an array called so I can do some research?? Or are there more examples somewhere? Would it just be like $tasktab.add(“value”)??


      Hi Conrad, that is correct.
      The $taskTab is a hash table. Some of the reasons these tables are useful; they allow table lookups based on a ‘key’ and you can easily the remove an entry from such a hash table.
      There is a good intro in Tobias’s free ebook in the Arrays and Hashtables chapter.


    Sadly PowerCLI 4.1 seems to have bug. The Id field returned by -Runasync tasks is always null and can not be used to check if the task has been completed.
    Is there any workaround? I’m thinking in sending SDK calls directly but it is a disadvantage because usign PowerCLI itself is much more easy.



    I get an error when I try to run the code:

    Method invocation failed because [System.Management.Automation.PSCustomObject] doesn’t contain a method named ‘IsTaskExist’.
    At :line:33 char:30
    + if ( $this.IsTaskExist <<<< ($taskName) -eq $true )

    Any ideas how to fix this?


      @Luci, are you sure you are using the script from this post ?
      Perhaps you could include the code you are trying to run.

    Ray Van Dolson

    @Doug Youd

    Also running into the same thing Doug mentioned. This seems to be behavior new to PowerCLI 4.1 whereas things work as expected with PowerCLI 4.0. I posted regarding this issue here as well.

    E. Jacob Hayes


    I finally got around to using your Hash code above instead of foreach loops. You ought to put a donate button on your page. Some of us out in the community have benefited greatly from your contributions & I’d personally like to toss a tip over your way.

    Doug Youd

    Hey Muc,

    I’ve integrated this logic into a script, but doing a move-vm task in async mode…. However the task object I get back has a null Id and the UID is somethign like “Uid : /Local=/ClientSideTask=d540cbd2-f083-495e-bee3-403f4c69c56d/”, I’m stumped how I can match on something like that.

    Any ideas?


    Doug Youd

    Thanks Luc,

    Appreciate your feedback. Cya round 🙂

    Doug Youd

    Hi Luc,

    Thanks for this… I’m using it as part of my clone-to-test scripts, which I’ll blog about shortly.

    Keep up the good work. 🙂



      Thanks Doug.
      I love the XML posts on your blog.


    Yes you did thanks and it worked 🙂


    I need a little help here used the script above and it worked great the first time. The second time I ran it it created the VMs to the wrong Datastore!! Can you let me know if I can specify the datastore I want?

    BTW Great work!
    thanks in advance


      I already answered in the thread you created in the PowerCLI community.
      You can add the -Datastore parameter on the New-VM cmdlet.
      This will create all clones on that specific datastore.

    Mike Foley

    Hi Luc,

    Thanks for replying. Since I last posted that, I decided to re-write the script to forgo using a CSV file. The script is set up to create a LARGE number of VM’s. Instead of the CSV file with all the info, I now take the number of VM’s to be created and divide it by the number of hosts I want to create the VM’s on. It’ll split the number (need a little work there to round out the numbers) across all the hosts. I’m “using” some of your code to add the ability to deal with starting all these asynchronous VM’s. Here’s what I have for the loop that creates them.

    $vm_count = 0
    $vmname_count = 0
    foreach ($host_system in $hosts) {
    write-host “———————————Creating VM’s on $host_system —————————– ”
    while ($vm_count -le $num_vms_per_host) {
    #Write-Host “The vmnamecount is” $vmname_count
    $VM_Name = $vm_prefix + $vmname_count
    Write-Host “The vm name is” $VM_Name
    $taskTab[(New-VM -Name $VM_Name -Template $template -VMHost $host -Datastore $Datastore -DiskStorageFormat Thin -RunAsync).Id] = $VM_Name
    $vm_count = 0

    I need to test the code (there’s much more in the full script) but I think the above will work.

    Thanks for your help (as always!)

    Mike Foley

    Hi Luc,

    Instead of foreach($Name in $newVmList) how would the code look if I was using Import-CSV | foreach {blahblah} ? I tried and kept banging my head. 🙂



      Hi Mike, You have 2 options for your CSV file, with a header row and without a header row.
      1) With a header row
      The CSV file looks like this


      The ForEach loop then becomes
      # Create all the VMs specified in the CSV
      Import-Csv "C:\NewVMList-with-header.csv" | %{
      $taskTab[(New-VM -VM (Get-VM $modelVm) -Name $Name -VMHost (Get-VMHost -Name $esxName) -RunAsync).Id] = $Name

      2) Without a header row
      The CSV file looks like this


      The ForEach loop then becomes
      # Create all the VMs specified in the CSV
      Import-Csv "C:\NewVMList-with-header.csv" -Header Name | %{
      $taskTab[(New-VM -VM (Get-VM $modelVm) -Name $Name -VMHost (Get-VMHost -Name $esxName) -RunAsync).Id] = $Name

      With both options you can remove line 3 of the original script.

      I hope this helped.

    Wade Holmes

    Great info, very useful. Love the detailed annotations. In the script code, did you mean for variable $taskTab to be called $hashTab?


      Thanks Wade.
      You are of course right. The annotation for line 4 should have said $taskTab instead of $hashTab.
      Thanks for the attentive reading, I corrected it in the post.

Leave a Reply

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


This site uses Akismet to reduce spam. Learn how your comment data is processed.