Task Data Mining – An improved Get-Task

Quite frequently there are questions in the VMTN PowerCLI Community for scripts that report on the tasks that ran in a vSphere environment.

The PowerCLI pssnapin provides a Get-Task cmdlet, but that only provides information about the recent tasks. An alternative is to use the Get-VIEvent cmdlet and extract all the TaskEvent entries.

But why not use the TaskHistoryCollector and it’s methods ? It provides many filtering options, and since this filtering is done in vSphere itself, this way of working is inherently much faster than using a filter in your script.

In analogy with the Get-VIEventPlus function, I published in my Get the vMotion/svMotion history post, here is the Get-TaskPlus function !

The Script


Line 62: This parameter should normally not be used. But in larger environments, settings the TaskHistoryCollector window to a value higher than 100, might improve the performance of the function

Line 66-110: An internal function that will extract the data from the returned TaskInfo objects.

Line 76-81: When the function sees that is running in a PowerShell v3 engine, it will use the [ordered] attribute.

Line 107: This is where the actual object is returned

Line 112-141: The TaskFilterSpec object is populated with the parameters that were passed to the function.

Line 123: The “self” recursion option does not seem to work for a number of managed objects. If you want to use a HostSystem or a ClusterComputeResource as the Entity, select the Recurse parameter as well. And filter out the children from the returned objects.

Line 145-146: Since the function can also be run against an ESXi server, we use the ServiceInstance to find the TaskManager object.

Line 148-153: In the TaskManager object, under the RecentTask property, there are pointers to Task objects. These are tasks that are queued, running or that have ended during the last minutes.

Line 157-163: If the Reverse switch is True, the TaskInfo objects are read newest to oldest. The script stores the method to read in a variable, this to avoid having to execute the Reverse test several times.

Line 165: With the Invoke method the function calls the selected method.

Line 172: The TaskHistoryCollector is destroyed. This is a best practice when working with HistoryCollectors, since a session can only have a limited number of such Collectors.

Sample Usage

The function is easy to use.

In it’s simplest form it will return the default MaxSamples number (100) of tasks.

Due to the number of properties that are returned, it is much easier to save the results to a file, for example an XLSX file. You can use the Export-Xlsx function from my Export-Xlsx, the sequel, and ordered data post.

The result looks something like this.


As you might notice, the properties are obviously not in the same order as we assigned them in the function. That is because the previous spreadsheet was created on a PowerShell v2 session. When we call the function in a PowerShell v3 session, the properties will be ordered.

Let’s also ask some more details in this invocation of the function.

In the result we get the columns/properties in the order they were specified in the function, and there are many additional properties present.


The function allows you to select the returned tasks on several criteria.

For example, all tasks started by a specific Alarm during the last 7 days.

Or all the tasks that were started by a specific user, newest to oldest task.

Or all the task that gave an error between 2 and 4 days ago.

Start exploring the possibilities, and I would love to hear when you have used the function in one of your automation scripts.

Enjoy !



    Hi Luc, Thanks for the function. Do you know if you can create custom vctasks – e.g. to track something that is not coming from a _task method?

    Cheers, Corty


      Hi Corty,
      Afaik you can not start your own tasks on the vCenter.

    Joy Mamer


    How would I alter this to get just the deleted vms with a Destroy_Task? I’d also like to go back more than a month.

    Name : Destroy_Task
    Description :
    DescriptionId : VirtualMachine.destroy
    Task Created : 1/17/2019 6:17:56 PM
    Task Started : 1/17/2019 6:17:56 PM
    Task Ended : 1/17/2019 6:17:56 PM
    State : success
    Result :
    Entity : presitzzcgw0
    VIServer :
    Error :
    Cancelled : N
    Reason : User
    AlarmName :
    AlarmEntity :
    ScheduleName :
    User : XXXX\jmamer


      Hi Joy,
      The easiest would be to use the Get-ViEvent cmdlet.
      Something like this

      $start = (Get-Date).AddHours(-1)

      Get-VIEvent -Start $start -MaxSamples ([int]::MaxValue) |
      Where{$_ -is [VMware.Vim.TaskEvent] -and $_.Info.Name -eq 'Destroy_Task'}

      You can adapt the value of the $start variable to go further back in time.
      You can’t of course go back further than the duration for which VMs are kept.

    Greg Moyses

    I cant seem to use the entity parameter

    “Get-TaskPlus : Cannot process argument transformation on parameter ‘Entity’. Cannot convert the “SERVER-NAME”
    value of type “System.String” to type “VMware.VimAutomation.ViCore.Impl.V1.Inventory.InventoryItemImpl”.
    At C:\Untitled2.ps1:178 char:22
    + Get-TaskPlus -Entity SERVER-NAME -Details -Keys -MaxSamples 15000 …
    + ~~~~~~~~~~~~~~~
    + CategoryInfo : InvalidData: (:) [Get-TaskPlus], ParameterBindingArgumentTransformationException
    + FullyQualifiedErrorId : ParameterArgumentTransformationError,Get-TaskPlus””

    Any ideas?



      Hi Greg,
      The Entity parameter expects a .Net object, not a string I’m afraid.
      Something like

      $esx = Get-VMHost -Name MyEsx
      Get-TaskPlus -Entity $esx ....

    Everton Poyato

    Is it possible to put a column with the task initiator?

    Wayne S.

    Is it “normal” to have the task return with GTM rather than the time that shows in the task log? Was trying to get a report of when our backups start and finished and my start and end times are “not correct” based on what is returned. Do I have to make the correction after the fact or is there a way to have it return EST rather than GMT?

    for example start
    Entity Task Started Task Ended
    —— ———— ———-
    datastore1 9/24/2017 9:00:44 PM 9/24/2017 9:03:32 PM

    The actual start and and end times are.
    Entity Task Started Task Ended
    —— ———— ———-
    datastore1 9/24/2017 5:00:44 PM 9/25/2017 5:03:32 AM


      Hi Wayne,
      Yes, that’s normal.
      All timestamps in Task and Events, but also in the logs, are in UTC.
      The view in the Web Client converts to your local timezone. In a script you will have to do that by calling the ToLocalTime() method on the DateTime object.

        Wayne S.

        Thanks. just modified the lines…

        $object.Add(“Task Started”,$_.StartTime.tolocaltime())

        if($Details){$object.Add(“Task Ended”,$_.CompleteTime.tolocaltime())}


    Fantastic, as usual!! Found a need to use this type of data mining just today and of course Luc had already done all the heavy lifting. I could not remember the names of the two vm’s I moved when testing some latency difference between backend storage though I do remember the destination datastore. A simple one liner did the trick:
    get-vm -Datastore destdsname |Get-MotionHistory -Days 7

    Robert van den Nieuwendijk

    The Get-TaskPlus function closes my PowerCLI console. It happens in the do-while loop in the lines 164-170. Probably by the exit command in line 166 if(!$tasks){exit}. I am using PowerCLI 6.3 Release 1 on PowerShell 4.0 connected to a vCenter v5.5 server.


      Thanks for noticing that Robert.
      I changed it.


    Is it possible to get the status, as well as the state? Eg: The state being:Error and the StatusL Unable to start operation

    Anton Coleman

    Hi Luc,

    I like the script you’ve provided a lot. It seems though, that I’m having an issue where the TaskHistoryCollector session isn’t being destroyed (I assume). This is a problem when I rerun this function multiple times within a short period, as the collector will not show up and recent tasks performed. I know the Get-Task will show recent, but the script I’m writing, will probably generate about 100 tasks in less than an hour, and I need a solid way to track the tasks so I can write the success/error, and task description to a log. Thoughts? Thanks for all that you do.


      Thanks Anton.
      The problem is that DestroyCollector doesn’t give any feedback.
      One, not so elegant, way of testing if the Collector is actually destroyed, could be to try and use it (for example by doing a ResetCollector). If the Collector is not present anymore, you should get an Exception.
      Using that Exception you could include a Try-Catch in the code and make sure the Collector is actually gone before proceeding.

      Another solution could be to create a global collector ($global:tCollector), and then reuse that one in each call to the function.
      This avoids the overhead of setting up and removing the collector at each call of the function.



    I’ve just run this script in its most basic form, Get-TaskPlus and I get absolutely nothing.

    Peter vB

    Nice script! One issue though, I was trying the -Realtime switch but it gave me some trouble.

    Firstly I changed line 151 to something similar to line 168, it seems that the $tasks variable was not present. After that there was a type mismatch between Task and TaskInfo so I changed line 149 to these three lines:

    $taskObjects = Get-View $tskMgr.recentTask
    $tasks = @()
    $taskObjects | foreach { $tasks += $_.Info }

    That seemed to resolve the type mismatch but only a few recent tasks are returned. There still is about a two hour gap between the most recent task in vSphere client and the list your script returns.
    Any ideas?


      Hi Peter,
      I’ll have a look


        Same thing reported. I did the changes that Peter suggested and that improved the -Realtime switch. I do get an odd sorting based on time of Task Started, though. Still, much more helpful than get-task. thanks!

        Samuel Wambach

        I had the same issue. I was able to get all of the tasks returned by making the above changes and setting ” [switch]$Reverse = $false ” on line 57. There seems to be a problem with the block at line 157 so I just disabled it.

        Also, thank you for sharing this script Luc. This was by far the best example of using the TaskHistoryCollector I could find and gave me a much clearer picture of how it worked. Thank you!


    Hi Luc,

    Awesome script, just what I needed to review many vCenter tasks. However i noticed some strange issue.
    I want to get the events from all esxi hosts in my vcenter,
    So I do :
    $ents = get-vmhost *
    foreach ($ent in $ents) {
    get-taskplus -entity $ent

    I get no output from the function. i looked at the code and I\’m not sure, but could it be that the object is cast a different type rather than what vmhost is returning.
    If I do get member on $ent i get this.
    TypeName: VMware.VimAutomation.ViCore.Impl.V1.Inventory.VMHostImpl

    The entity variable is cast as:


      Strange, I just ran your script, and I seem to get the TaskEvents from all my ESXi nodes back.
      Does it work when you only use 1 ESXi node, for example
      Get-VMHost -Name MyEsx | Get-TaskPlus



    I find a bug.
    In the vsphere 5.5, the script is not work.
    The result has a fixed time period.


      Thanks for letting me know.
      Do you have any more information ?
      Is there an error message ?


    Not sure what is going on but I have ran this function three different times and each time it kills my PS console.

    PS v3
    PowerCLI 5.1 Release 2 build 1012425

    Example commands tested:
    Get-TaskPlus -Entity Host1
    Get-TaskPlus -Entity Host1 -Start (Get-Date).AddDays(-3)
    Get-TaskPlus -Entity Host1 -Start (Get-Date).AddDays(-1)

    Any ideas?


      Are there any messages ?
      Did you run that from the PowerCLI prompt ? Or something else ?


        It was closing the console so fast I had to create a transaction log. Here is the error. It appears that when I use the -Start parameter it is expecting a value for -Finish.

        Get-TaskPlus -Entity $entity -Start (Get-Date).AddDays(-1) -MaxSamples 10
        Exception setting “endTime”: “Cannot convert null to type “System.DateTime”.”
        At line:126 char:7
        + $filter.Time.endTime = $Finish
        + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
        + CategoryInfo : NotSpecified: (:) [], SetValueInvocationExceptio
        + FullyQualifiedErrorId : ExceptionWhenSetting


          Hi jrob24, that is indeed an issue.
          Can you update the code where the Start and Finish are place in the filter (lines 129-134) by the following code

          if($Start -or $Finish){
          $filter.Time = New-Object VMware.Vim.TaskFilterSpecByTime
          if(!$Start){$Start = Get-Date 1/1/1970}
          $filter.Time.beginTime = $Start
          if(!$Finish){$Finish = Get-Date}
          $filter.Time.endTime = $Finish
          $filter.Time.timeType = [vmware.vim.taskfilterspectimeoption]::startedTime

          That way you should be able to only pass a Start parameter.

          For the other problem I don’t have a reply.
          What is in the $entity variable ? A VM, a folder… ?
          Does it crash for any entity ?
          Can you try without the Entity parameter


        I tested it with both a start and finish date, it killed the console and no error message. Here is transcript:
        Windows PowerShell transcript start
        Start time: 20140205163427
        Transcript started, output file is trans.txt

        PowerCLI C:\temp> Get-TaskPlus -Entity $entity -Start (Get-date).AddDays(-2) -Finish (Get-Date)
        Windows PowerShell transcript end
        End time: 20140205163450

    Trevor Benson

    I love the get-taskplus script. One thing I noticed however, when I typo’d a -UserName it would kill my entire script and the shell/window I was working in. I think Line 166 should read be altered to
    so that in the event there is no return values, your shell does not close, but rather the script exits and returns back to the prompt with zero results. Did I miss some reason why Get-TaskPlus should not Break or Return, but rather exit and close the window? Let me know if I missed something, as I only took a brief look after I had an invalid username and it closed my shell down.


      Thanks Trevor, that is indeed a use case I didn’t test.
      No, you didn’t miss anything, I’ll update the script.

    harshvardhan gupta

    i need help in creating a powershell script to change vm’s iops, boot delay, scsi to paravirtualiz and thick disk to eagerly zeroed, i collected things from internet and created something like this but it is throwing some error.

    Please help me out.
    #Generated Form Function
    function GenerateForm {
    # Code Generated By: SAPIEN Technologies PrimalForms (Community Edition) v1.0.10.0
    # Generated On: 6/18/2013 10:10 AM
    # Generated By: ts-harshavardh.gupta

    #region Import the Assemblies
    [reflection.assembly]::loadwithpartialname(“System.Windows.Forms”) | Out-Null
    [reflection.assembly]::loadwithpartialname(“System.Drawing”) | Out-Null

    #region Generated Form Objects
    $form1 = New-Object System.Windows.Forms.Form
    $button4 = New-Object System.Windows.Forms.Button
    $button3 = New-Object System.Windows.Forms.Button
    $button2 = New-Object System.Windows.Forms.Button
    $button1 = New-Object System.Windows.Forms.Button
    $InitialFormWindowState = New-Object System.Windows.Forms.FormWindowState
    #endregion Generated Form Objects

    #Generated Event Script Blocks
    #Provide Custom Code for events specified in PrimalForms.
    #TODO: Place custom script here
    #change IOPS to 500
    #$vmName = “MyVM”
    $_HEADER = “hostname”,”os”,”type”
    $vmName=Import-Csv ./vm_list.csv -Header $_HEADER
    $DiskLimitIOPerSecond = 500
    # $DiskLimitIOPerSecond = -1 # Unlimited

    foreach($vmNames in $vmName){
    $vm = Get-VM -Name $vmName
    $spec = New-Object VMware.Vim.VirtualMachineConfigSpec
    $vm.ExtensionData.Config.Hardware.Device |
    where {$_ -is [VMware.Vim.VirtualDisk]} | %{
    $dev = New-Object VMware.Vim.VirtualDeviceConfigSpec
    $dev.Operation = “edit”
    $dev.Device = $_
    $dev.Device.StorageIOAllocation.Limit = $DiskLimitIOPerSecond
    $spec.DeviceChange += $dev


    #TODO: Place custom script here
    #Change Boot Delay
    $value = “10000”

    $vmConfigSpec = New-Object VMware.Vim.VirtualMachineConfigSpec
    $vmConfigSpec.BootOptions = New-Object VMware.Vim.VirtualMachineBootOptions
    $vmConfigSpec.BootOptions.BootDelay = $value

    Get-Content -Path c:\scripts\vm_list.csv | %{
    (Get-VM -Name $_).Extensiondata.ReconfigVM_Task($vmConfigSpec)


    #TODO: Place custom script here
    #Change scsi to paravirtualized
    #$vmName = “MyVM”
    $_HEADER = “hostname”,”os”,”type”
    $vmName=Import-Csv ./vm_list.csv -Header $_HEADER
    $ctrlName = “SCSI controller 0”

    foreach($vmNames in $vmName){
    $vm = Get-VM $vmName | Get-View

    # Get the controller and the devices connected to it
    $vm.Config.Hardware.Device | where {$_.DeviceInfo.Label -eq $ctrlName} | % {
    $oldCtrl = $_
    $ctrlKey = $_.Key
    $devs = @()
    $_.Device | % {
    $devKey = $_
    $vm.Config.Hardware.Device | where {$_.Key -eq $devKey} | % {
    $devs += $_

    # Create the specification for the device changes
    $spec = New-Object VMware.Vim.VirtualMachineConfigSpec

    # Remove old controller
    $old = New-Object VMware.Vim.VirtualDeviceConfigSpec
    $old.device = $oldCtrl
    $old.operation = “remove”
    $spec.DeviceChange += $old

    # Update the devices connected to the controller
    $devs | % {
    $dev = New-Object VMware.Vim.VirtualDeviceConfigSpec
    $dev.device = $_
    $dev.device.ControllerKey = -100
    $dev.operation = “edit”
    $spec.DeviceChange += $dev

    # Add new controller
    $new = New-Object VMware.Vim.VirtualDeviceConfigSpec
    $new.Device = New-Object VMware.Vim.ParaVirtualSCSIController
    $new.Device.Key = -100
    if($defaultVIServer.Build -ge ‘258902’){
    $new.Device.ControllerKey = $oldCtrl.ControllerKey
    $new.Device.UnitNumber = $oldCtrl.UnitNumber
    $new.operation = “add”
    $spec.DeviceChange += $new


    #TODO: Place custom script here


    {#Correct the initial state of the form to prevent the .Net maximized form issue
    $form1.WindowState = $InitialFormWindowState

    #region Generated Form Code
    $System_Drawing_Size = New-Object System.Drawing.Size
    $System_Drawing_Size.Height = 374
    $System_Drawing_Size.Width = 291
    $form1.ClientSize = $System_Drawing_Size
    $form1.DataBindings.DefaultDataSourceUpdateMode = 0
    $form1.Name = “form1”
    $form1.Text = “Change VM settings”

    $button4.DataBindings.DefaultDataSourceUpdateMode = 0

    $System_Drawing_Point = New-Object System.Drawing.Point
    $System_Drawing_Point.X = 57
    $System_Drawing_Point.Y = 309
    $button4.Location = $System_Drawing_Point
    $button4.Name = “button4”
    $System_Drawing_Size = New-Object System.Drawing.Size
    $System_Drawing_Size.Height = 43
    $System_Drawing_Size.Width = 186
    $button4.Size = $System_Drawing_Size
    $button4.TabIndex = 3
    $button4.Text = “change disk to Eagerly Zeroed”
    $button4.UseVisualStyleBackColor = $True


    $button3.DataBindings.DefaultDataSourceUpdateMode = 0

    $System_Drawing_Point = New-Object System.Drawing.Point
    $System_Drawing_Point.X = 57
    $System_Drawing_Point.Y = 230
    $button3.Location = $System_Drawing_Point
    $button3.Name = “button3”
    $System_Drawing_Size = New-Object System.Drawing.Size
    $System_Drawing_Size.Height = 47
    $System_Drawing_Size.Width = 186
    $button3.Size = $System_Drawing_Size
    $button3.TabIndex = 2
    $button3.Text = “change IOPS to 500”
    $button3.UseVisualStyleBackColor = $True


    $button2.DataBindings.DefaultDataSourceUpdateMode = 0

    $System_Drawing_Point = New-Object System.Drawing.Point
    $System_Drawing_Point.X = 57
    $System_Drawing_Point.Y = 143
    $button2.Location = $System_Drawing_Point
    $button2.Name = “button2”
    $System_Drawing_Size = New-Object System.Drawing.Size
    $System_Drawing_Size.Height = 55
    $System_Drawing_Size.Width = 186
    $button2.Size = $System_Drawing_Size
    $button2.TabIndex = 1
    $button2.Text = “Change scsi to paravirtualized”
    $button2.UseVisualStyleBackColor = $True


    $button1.DataBindings.DefaultDataSourceUpdateMode = 0

    $System_Drawing_Point = New-Object System.Drawing.Point
    $System_Drawing_Point.X = 57
    $System_Drawing_Point.Y = 64
    $button1.Location = $System_Drawing_Point
    $button1.Name = “button1”
    $System_Drawing_Size = New-Object System.Drawing.Size
    $System_Drawing_Size.Height = 49
    $System_Drawing_Size.Width = 186
    $button1.Size = $System_Drawing_Size
    $button1.TabIndex = 0
    $button1.Text = “Change Boot Delay”
    $button1.UseVisualStyleBackColor = $True


    #endregion Generated Form Code

    #Save the initial state of the form
    $InitialFormWindowState = $form1.WindowState
    #Init the OnLoad event to correct the initial state of the form
    #Show the Form
    $form1.ShowDialog()| Out-Null

    } #End Function

    #Call the Function

    Kevin D

    Great script, but I found one bug. As written, it doesn’t work for users in my environment. I replaced lines 138-140 with the following:

    $userNameFilterSpec = New-Object VMware.Vim.TaskFilterSpecByUserName
    $userNameFilterSpec.UserList = $UserName
    $filter.UserName = $userNameFilterSpec


      Hi Kevin, thanks for spotting that bug.
      I updated the script accordingly.

    Lenny Chauhan

    I am new to PowerCLI. I need a script to list all the eagerzeroedthick vmdk files. Would appreciate your assistance.


      Hi Lenny,
      Try something like this

      Get-VM | %{
      $vmName = $_.Name
      Get-HardDisk -VM $_ | where {!$_.ExtensionData.Backing.EagerlyScrub} |
      Select @{N="VM";E={$vmName}},Name

Leave a Reply

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


two × 4 =

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