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 !

Update February 13th 2020

  • Added logic to break out of do-while loop and destroy the TaskCollector to avoid issues with max 32 collectors

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 166: When there are no tasks returned, the script breaks out of the do-while loop.

Line 171: The TaskHistoryCollector is destroyed. This is a best practice when working with HistoryCollectors, since a session can only have a limited number (32) 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 LucD,
    thanks for the script.
    I have a question, I used it but I don’t display the tasks (completed, running, etc.) for the current day. The script displays only the tasks of yesterday or of the previous days.


      Did you use the Realtime switch?

      That should “when true the most recent tasks are also returned.

        Fabio Storni

        Yes but nothing


          Strange, I just tested in vSphere 8.* and it seems to work for me.

          Note that the default for MaxSamples is 100.
          If you have many tasks and you haven’t specified MaxSamples, the most recent tasks will be missing.

          How did you call the function?
          Which aparmeters did you use?


            PS C:\Users\fabio.storni> $start = (Get-Date).AddHours(-12)
            PS C:\Users\fabio.storni>
            PS C:\Users\fabio.storni> $start

            Tuesday, October 3, 2023 8:43:15 AM

            PS C:\Users\fabio.storni>
            PS C:\Users\fabio.storni> Get-TaskPlus -Start $start -Realtime
            PS C:\Users\fabio.storni>

            If I use Get-TaskPlus …. it show only task until yesterday

            PS C:\Users\fabio.storni> Get-TaskPlus

            Name : RelocateVM_Task
            Description :
            Task Started : 10/2/2023 8:03:52 PM
            State : success
            Result :
            Entity : MasterImagew11v2
            VIServer : vca07.pollaio.lan
            Error :

            Name : RelocateVM_Task
            Description :
            Task Started : 10/2/2023 8:03:52 PM
            State : success
            Result :
            Entity : ubuntu_masterv2
            VIServer : vca07.pollaio.lan
            Error :

            Name : RelocateVM_Task
            Description :
            Task Started : 10/2/2023 8:03:52 PM
            State : success
            Result :
            Entity : New Virtual Machine
            VIServer : vca07.pollaio.lan
            Error :

            But i have a task from today

            Task Name Target Status Initiator Start Time Completion Time Execution Time Server
            Relocate virtual machine ubuntu_masterv2 Completed administrator@corp.local 10/02/2023, 4:43:33 PM 10/02/2023, 4:45:44 PM 2 m 11 s vca07.pollaio.lan
            Initiate vMotion receive operation MasterImagew11v2 Completed System 10/02/2023, 4:22:34 PM 10/02/2023, 4:24:37 PM 2 m 2 s vca07.pollaio.lan
            Rescan VMFS viesxi02v7.pollaio.lan Completed administrator@corp.local 09/28/2023, 12:24:04 PM 09/28/2023, 12:24:06 PM 2 s vca07.pollaio.lan
            Scheduled hardware compatibility check vca07.pollaio.lan Completed VMware vSphere Lifecycle Manager HCL Validation 09/29/2023, 6:30:01 AM 09/29/2023, 6:30:01 AM 50 ms vca07.pollaio.lan
            Move entities VM PROD Completed administrator@corp.local 10/02/2023, 4:41:15 PM 10/02/2023, 4:41:15 PM 9 ms vca07.pollaio.lan
            Initiate vMotion receive operation MasterImagew11v2 Completed System 10/02/2023, 9:53:13 PM 10/02/2023, 9:57:09 PM 3 m 55 s vca07.pollaio.lan
            Relocate virtual machine MasterImagew11v2 Completed administrator@corp.local 09/28/2023, 4:45:34 PM 09/28/2023, 4:47:58 PM 2 m 24 s vca07.pollaio.lan
            Update network configuration viesxi02v7.pollaio.lan Completed administrator@corp.local 09/28/2023, 11:50:19 AM 09/28/2023, 11:50:20 AM 1 s vca07.pollaio.lan
            Move entities VM DEV Completed administrator@corp.local 10/03/2023, 6:02:50 PM 10/03/2023, 6:02:50 PM 38 ms vca07.pollaio.lan
            Relocate virtual machine ubuntu_masterv2 Completed administrator@corp.local 10/02/2023, 3:07:25 PM 10/02/2023, 3:10:46 PM 3 m 20 s vca07.pollaio.lan
            Relocate virtual machine MasterImagew11v2 Completed administrator@corp.local 09/28/2023, 2:43:34 PM 09/28/2023, 2:45:46 PM 2 m 12 s vca07.pollaio.lan
            Initiate vMotion receive operation ubuntu_masterv2 Completed System 09/28/2023, 1:23:58 PM 09/28/2023, 1:26:00 PM 2 m 1 s vca07.pollaio.lan
            Relocate virtual machine ubuntu_masterv2 Completed administrator@corp.local 09/28/2023, 5:06:28 PM 09/28/2023, 5:08:31 PM 2 m 3 s vca07.pollaio.lan
            Initiate vMotion receive operation ubuntu_masterv2 Completed System 09/28/2023, 4:58:49 PM 09/28/2023, 5:01:56 PM 3 m 6 s vca07.pollaio.lan
            Initiate vMotion receive operation ubuntu_masterv2 Completed System 10/03/2023, 6:00:36 PM 10/03/2023, 6:02:35 PM 1 m 58 s vca07.pollaio.lan
            Move entities Datacenter-vSAN7 Completed administrator@corp.local 10/02/2023, 8:36:40 PM 10/02/2023, 8:36:40 PM 6 ms vca07.pollaio.lan
            Initiate vMotion receive operation MasterImagew11v2 Completed System 09/28/2023, 4:58:45 PM 09/28/2023, 5:02:19 PM 3 m 34 s vca07.pollaio.lan
            Initiate vMotion receive operation MasterImagew11v2 Completed System 09/28/2023, 1:20:59 PM 09/28/2023, 1:23:30 PM 2 m 31 s vca07.pollaio.lan


              Can you check if there are any recent Tasks present?

              $si = Get-View ServiceInstance
              $taskMgr = Get-View $si.Content.TaskManager

              if($taskMgr.RecentTask.Count -ne 0){
              Get-View $taskMgr.RecentTask
              else {
              Write-Host "No recent Tasks"

                Fabio Storni

                PS C:\Users\fabio.storni> Connect-VIServer vca07.pollaio.lan -Credential $credential

                Name Port User
                —- —- —-
                vca07.pollaio.lan 443 CORP.LOCAL\Administrator

                PS C:\Users\fabio.storni>
                PS C:\Users\fabio.storni> $si = Get-View ServiceInstance
                $taskMgr = Get-View $si.Content.TaskManager

                if($taskMgr.RecentTask.Count -ne 0){
                Get-View $taskMgr.RecentTask
                else {
                Write-Host “No recent Tasks”
                No recent Tasks
                PS C:\Users\fabio.storni>

                The tasks that I want check are MOVE-VM with RUNASYNC


                  So there are no Tasks that completed during the last 10 minutes.

                  Another possible cause I can think of is that due to the default MaxSamples of 100 some recent Tasks are not included.
                  You might want to use that parameter to give it a try.

                  Get-TaskPlus -Start $start -Realtime -MaxSamples ([int]::MaxValue)


    Hi Luc,

    Is there a way i can use this function to check when vmtools was updated for a vm.


    Hello LuсD.
    Perhaps line 151 is missing a variable $Tasks.


      No, it’s not.
      The resulting objects are placed in the pipeline.


        Sorry if I’m wrong.
        But the function Get-TaskDetails must be passed an array of “VMware.Vim.TaskInfo” objects.
        But there is no variable to select a range of objects:
        Get-TaskDetails -Tasks[0..($selectNr – 1)]


          Yes, you are right.
          The code has been updated

    Kelvin Wong

    Hi LucD,

    I copied the script and tried to execute as follows, but no tasks is returned
    get-vm myhost1
    get-taskplus -entity $vm

    When I debugged the scripts, I can see that $tcolletor did have a list of tasks


    LatestPage Filter MoRef
    ———- —— —–
    {PowerOnVM_Task, ShutdownGuest, PowerOnVM_Task, PowerOnVM_Task…} VMware.Vim.TaskFilterSpec TaskHistoryCollector-session[522c80d1-7e83-7344-010f-02e21f633718]52945257-7dbc-4137-ff7b-ac8…

    But after “$tasks = $taskReadOp.Invoke($WindowSize)” executes. No tasks is returned and the function breaks out

    when I check the invoke method with get-member, it seems to require a system.object instead of a string
    Invoke Method System.Object Invoke(Params System.Object[] arguments)

    Any advise?

    I am on powercli 12.x and powershell 5.x


      The $WindowSize variable contains an Int, not a String.
      And either of these is System.Object.

      When you call the function without any parameters except for the Entity parameter, the function will only return the last 100 events.
      If in those 100 events there is no Task for that specific VM, nothing will be returned.
      What you see in the $tCollector are the most recent Tasks for any Entity.

    Jan Klok

    Can the tasks “Apply Storage DRS recommendations” be reported by this script?


      Not as a parameter on the Get-TaskPlus call, but you can filter the results (for example with a Where-clause), to only show results where the Name is ‘ApplyStorageDrsRecommendation_Task’.


    Hi LucD,
    Apologize if I missed it somewhere, the output is not giving me latest tasks, only showing until 2 days ago. Today is May 24th but the report only shows until May 22nd, however vCetner has the tasks on May 23rd and 24th. I tried to see if i missed anything but didn’t find any.

    This is what i am using
    Get-TaskPlus -Entity $vm -Details | Export-Xlsx -Path “C:\Task-Plus\task-details.xlsx”

    tried with these as well but same result
    Get-TaskPlus -Entity $vm -Details -Start (Get-Date).AddDays(-7) -Finish (Get-Date) | Export-Xlsx -Path “C:\Task-Plus\task-details.xlsx”

    Get-TaskPlus -Entity $vm -Details -Start (Get-Date).AddDays(-7) | Export-Xlsx -Path “C:\Task-Plus\task-details.xlsx”


      By default the function returns only 100 Tasks.
      You can change that by using the MaxSamples parameter.
      For example

      Get-TaskPlus -Entity $vm -Details -MaxSamples ([int]::MaxValue) | Export-Xlsx -Path “C:\Task-Plus\task-details.xlsx”

        Dipak Mohapatra

        Looks like It’s missing the exactly latest 10 lines of tasks for some reason, I ran it for multiple VMs and the result is same, missing exactly last 10 latest tasks.

        I can’t paste the screenshots here, else I would have shown it to you. 🙂


          Did you try with the Realtime switch?

    Buddy Bishop

    I’m using this script in an attempt to monitor a set of clonevm tasks that run for several minutes or longer. The intention is that when all clone tasks are completed, the VM’s will then be powered up.
    For some reason a few VM’s clone tasks are never seen/detected as existing. However, they are clearly (albeit slowly) underway in the vSphere console.
    I have nine clone tasks in progress. The script only shows seven.
    What I can I look for or adjust to account for this?


    Hello LucD,

    I am trying to retrieve those running tasks using Get-TaskPlus, but it’s not able to fetch those running tasks; however, Get-Task command is able to show those running tasks for a while and it disappeared suddenly while the tasks is still running.

    Is there way to monitor those running tasks until it completes successfully. For example, the tasks like vMotion, svMotion, Host Remediation, etc.

    Lucas Taves

    I’m trying to use this to get tasks from a template, I basically want to retrieve all mark VM as template tasks to get when the template was last set as template.

    However, using Get-TaskPlus -Entity $template (template is the direct result of a Get-TaskPlus -Entity $template call) returns nothing.

    I have debugged and it all seems correct, the entity is properly set as the filter, the task manager is set as well, and the $tasks = $taskReadOp.Invoke($WindowSize) call returns nothing.

    I can see the tasks from the web console, and I’ve tried a series of parameters but they don’t seem to make any difference in this case.

      Lucas Taves

      Found out what’s happening, when the taskColletor ($tCollector = Get-View ($tskMgr.CreateCollectorForTasks($filter))) is created the tasks I want are there, so I just made sure the tasks returned by $taskReadOp.Invoke($WindowSize) are added into the existing task list instead of replacing it entirely. Seems to be working now.


        Hi Lucas,
        Not sure what you changed.
        The tasks are retrieved in batches, and each task is then send to the Get-TaskDetails function.

        Note that tasks are (and I don’t know for what reason) not immediately available via the collector.
        I just experienced the same, but when I waited a bit, the results included the test tasks I ran.

        Name Description DescriptionId Task Created Task Started Task Ended State Result
        ---- ----------- ------------- ------------ ------------ ---------- ----- ------
        MarkAsVirtualMachine VirtualMachine.markAsVirtualMachine 6/2/2020 11:54:08 AM 6/2/2020 11:54:08 AM 6/2/2020 11:54:08 AM success
        MarkAsTemplate VirtualMachine.markAsTemplate 6/2/2020 11:34:08 AM 6/2/2020 11:34:08 AM 6/2/2020 11:34:08 AM success

    John Whittingham

    I know this post is old but it is still the best example of using the TaskHistoryCollector and one of the top Google results so I thought I would share my tweaks and corrections. Some of which are just incorporating the feedback from above, others looking more deeply into a couple of the issues raised.
    1. If no tasks are returned then the collectory was not being destroyed. Line 166 changed to break.
    2. Set default Start or End time if -Start or -End was specified (Taken from a reply by Luc above).
    3. Added -Server $viObject to Line 149 for the case when you pass in a VIServer object and not using the Global servers variable.
    4. Updated the code around the use of -Reverse. This was the most difficult to understand.
    a. Added collecting the events in .latestPage when reading previous becasue $tCollector.ResetColelctor() doesn’t set the page pointer to the newest task but the first task after the latestPage. Hence this collection was missing a 100 tasks (in it’s default settings).
    b. Reversed the order the tasks are passed to Get-TaskDetails when the order is oldest to newest. I think this is the strange order that some have noticed above. Pages where being collected in oldest to newest but within a page where being passed to Get-TaskDetails in newest to oldest hence every 100 tasks there would be a weird time jump.
    5. Separated -RealTime and -Filter actions into discrete calls using ParameterSetNames. For me collecting the task for 3 days ago and whats happening now just doesn’t make sense and adds time a gap in the results. also any filtering doesn’t apply to recent tasks so the 2 just seem separate. Take out the ParameterSets if you don’t like this.

    Here’s what I’ve ended up with:

    function Get-TaskPlus {
    Get-TaskPlus -Start (Get-Date).AddDays(-1)
    PS> Get-TaskPlus -Alarm $alarm -Details
    [VMware.VimAutomation.ViCore.Impl.V1.VIServerImpl[]]$Server = $global:DefaultVIServer,
    [int]$MaxSamples = 100,



    [switch]$Recurse = $false,





    [switch]$Reverse = $false,

    [int]$WindowSize = 100,

    begin {
    function Get-TaskDetails {
    begin {
    process {
    $tasks | % {
    $object = [ordered]@{ }
    $object.Add(“Name”, $_.Name)
    $object.Add(“Description”, $_.Description.Message)
    if ($Details) { $object.Add(“DescriptionId”, $_.DescriptionId) }
    if ($Details) { $object.Add(“Task Created”, $_.QueueTime) }
    $object.Add(“Task Started”, $_.StartTime)
    if ($Details) { $object.Add(“Task Ended”, $_.CompleteTime) }
    $object.Add(“State”, $_.State)
    $object.Add(“Result”, $_.Result)
    $object.Add(“Entity”, $_.EntityName)
    $object.Add(“VIServer”, $VIObject.Name)
    $object.Add(“Error”, $_.Error.ocalizedMessage)
    if ($Details) {
    $object.Add(“Cancelled”, (& { if ($_.Cancelled) { “Y” } else { “N” } }))
    $object.Add(“Reason”, $_.Reason.GetType().Name.Replace(“TaskReason”, “”))
    $object.Add(“AlarmName”, $_.Reason.AlarmName)
    $object.Add(“AlarmEntity”, $_.Reason.EntityName)
    $object.Add(“ScheduleName”, $_.Reason.Name)
    $object.Add(“User”, $_.Reason.UserName)
    if ($keys) {
    $object.Add(“Key”, $_.Key)
    $object.Add(“ParentKey”, $_.ParentTaskKey)
    $object.Add(“RootKey”, $_.RootTaskKey)
    New-Object PSObject -Property $object

    $filter = New-Object VMware.Vim.TaskFilterSpec
    if ($Alarm) {
    $filter.Alarm = $Alarm.ExtensionData.MoRef
    if ($Entity) {
    $filter.Entity = New-Object VMware.Vim.TaskFilterSpecByEntity
    $filter.Entity.entity = $Entity.ExtensionData.MoRef
    if ($Recurse) {
    $filter.Entity.Recursion = [VMware.Vim.TaskFilterSpecRecursionOption]::all
    else {
    $filter.Entity.Recursion = [VMware.Vim.TaskFilterSpecRecursionOption]::self
    if ($State) {
    $filter.State = $State
    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
    if ($UserName) {
    $filter.UserName = New-Object VMware.Vim.TaskFilterSpecByUserName
    $filter.UserName.userList = $UserName
    $filter.UserName.systemUser = $false
    $nrTasks = 0
    process {
    foreach ($viObject in $Server) {
    $si = Get-View ServiceInstance -Server $viObject
    $tskMgr = Get-View $si.Content.TaskManager -Server $viObject
    if ($Realtime) {
    $tasks = (Get-View $tskMgr.recentTask -Server $viObject).Info
    $selectNr = [Math]::Min($tasks.Count, $MaxSamples – $nrTasks)
    Get-TaskDetails -Tasks $tasks[0..($selectNr – 1)]
    $nrTasks += $selectNr
    else {
    $tCollector = Get-View ($tskMgr.CreateCollectorForTasks($filter)) -Server $viObject
    if ($Reverse.IsPresent) {
    $taskReadOp = $tCollector.ReadPreviousTasks
    $tasks = $tCollector.latestPage
    $selectNr = [Math]::Min($tasks.Count, $MaxSamples – $nrTasks)
    Get-TaskDetails -Tasks $tasks[0..($selectNr – 1)]
    $nrTasks += $selectNr
    do {
    $tasks = $taskReadOp.Invoke($WindowSize)
    if (!$tasks) { break }
    $selectNr = [Math]::Min($tasks.Count, $MaxSamples – $nrTasks)
    Get-TaskDetails -Tasks $tasks[0..($selectNr – 1)]
    $nrTasks += $selectNr
    } while ($nrTasks -lt $MaxSamples)
    else {
    $taskReadOp = $tCollector.ReadNextTasks
    do {
    $tasks = $taskReadOp.Invoke($WindowSize)
    if (!$tasks) { break }
    $selectNr = [Math]::Min($tasks.Count, $MaxSamples – $nrTasks)
    Get-TaskDetails -Tasks $tasks[($selectNr – 1)..0]
    $nrTasks += $selectNr
    } while ($nrTasks -lt $MaxSamples)


      Thanks John.

      On your 1st remark, I guess you missed my update today.
      In fact, it was not only the ‘break’ statement, but also the location where the Task collector is destroyed.
      As I see it, it should happen, as in my most recent code, after the do-while.


    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 ....


        i want to get tasks with respect to datastore


          Did you try passing a Datastore object on the Entity parameter?

    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.

      John Whittingham

      With this setting when there are no tasks and you call Get-TaskPlus several times (i.e. foreach loop on all hosts) then after 32 iterations you get an error trying to create the “Collector”. This is because the collector is not destroyed.
      I think Line 166 would be better altered to


        Hi John,
        Thanks for reporting that issue.
        I updated the code.

        There was in fact another issue with the placement of the call to the DestroyCollector method.

    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 *


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