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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 |
function Get-TaskPlus { <# .SYNOPSIS Returns vSphere Task information .DESCRIPTION The function will return vSphere task info. The available parameters allow server-side filtering of the results .NOTES Author: Luc Dekens .PARAMETER Alarm When specified the function returns tasks triggered by specified alarm .PARAMETER Entity When specified the function returns tasks for the specific vSphere entity .PARAMETER Recurse Is used with the Entity. The function returns tasks for the Entity and all it's children .PARAMETER State Specify the State of the tasks to be returned. Valid values are: error, queued, running and success .PARAMETER Start The start date of the tasks to retrieve .PARAMETER Finish The end date of the tasks to retrieve. .PARAMETER UserName Only return tasks that were started by a specific user .PARAMETER MaxSamples Specify the maximum number of tasks to return .PARAMETER Reverse When true, the tasks are returned newest to oldest. The default is oldest to newest .PARAMETER Server The vCenter instance(s) for which the tasks should be returned .PARAMETER Realtime A switch, when true the most recent tasks are also returned. .PARAMETER Details A switch, when true more task details are returned .PARAMETER Keys A switch, when true all the keys are returned .EXAMPLE PS> Get-TaskPlus -Start (Get-Date).AddDays(-1) .EXAMPLE PS> Get-TaskPlus -Alarm $alarm -Details #> param( [CmdletBinding()] [VMware.VimAutomation.ViCore.Impl.V1.Alarm.AlarmDefinitionImpl]$Alarm, [VMware.VimAutomation.ViCore.Impl.V1.Inventory.InventoryItemImpl]$Entity, [switch]$Recurse = $false, [VMware.Vim.TaskInfoState[]]$State, [DateTime]$Start, [DateTime]$Finish, [string]$UserName, [int]$MaxSamples = 100, [switch]$Reverse = $true, [VMware.VimAutomation.ViCore.Impl.V1.VIServerImpl[]]$Server = $global:DefaultVIServer, [switch]$Realtime, [switch]$Details, [switch]$Keys, [int]$WindowSize = 100 ) begin { function Get-TaskDetails { param( [VMware.Vim.TaskInfo[]]$Tasks ) begin { $psV3 = $PSversionTable.PSVersion.Major -ge 3 } process { $tasks | ForEach-Object { if ($psV3) { $object = [ordered]@{ } } else { $object = @{ } } $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 $filter.Time.beginTime = $Start $filter.Time.endTime = $Finish $filter.Time.timeType = [vmware.vim.taskfilterspectimeoption]::startedTime } if ($UserName) { $userNameFilterSpec = New-Object VMware.Vim.TaskFilterSpecByUserName $userNameFilterSpec.UserList = $UserName $filter.UserName = $userNameFilterSpec } $nrTasks = 0 } process { foreach ($viObject in $Server) { $si = Get-View ServiceInstance -Server $viObject $tskMgr = Get-View $si.Content.TaskManager -Server $viObject if ($Realtime -and $tskMgr.recentTask) { $tasks = Get-View $tskMgr.recentTask $selectNr = [Math]::Min($tasks.Count, $MaxSamples - $nrTasks) Get-TaskDetails -Tasks $tasks[0..($selectNr - 1)] $nrTasks += $selectNr } $tCollector = Get-View ($tskMgr.CreateCollectorForTasks($filter)) if ($Reverse) { $tCollector.ResetCollector() $taskReadOp = $tCollector.ReadPreviousTasks } else { $taskReadOp = $tCollector.ReadNextTasks } 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) $tCollector.DestroyCollector() } } } |
Annotations
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.
1 |
Get-TaskPlus |
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.
1 |
Get-TaskPlus | Export-Xlsx -Path C:\report.xlsx |
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.
1 2 |
Get-TaskPlus -Details -Keys -MaxSamples 1500 | Export-Csv C:\report2.xlsx -NoTypeInformation -UseCulture |
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.
1 2 3 |
$start = (Get-Date).AddDays(-7) $alarm = Get-AlarmDefinition -Name MyAlarm Get-TaskPlus -Alarm $alarm -Start $start -Details |
Or all the tasks that were started by a specific user, newest to oldest task.
1 |
Get-TaskPlus -UserName MyUser -Reverse |
Or all the task that gave an error between 2 and 4 days ago.
1 2 3 |
$start = (Get-Date).AddDays(-4) $finish = $start.AddDays(2) Get-TaskPlus -Start $start -Finish $finish -State error |
Start exploring the possibilities, and I would love to hear when you have used the function in one of your automation scripts.
Enjoy !
Fabio
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.
LucD
Did you use the Realtime switch?
That should “when true the most recent tasks are also returned.“
Fabio Storni
Yes but nothing
LucD
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?
Fabio
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
LucD
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
LucD
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)
Gautam
Hi Luc,
Is there a way i can use this function to check when vmtools was updated for a vm.
Macleud
Hello LuсD.
Perhaps line 151 is missing a variable $Tasks.
LucD
No, it’s not.
The resulting objects are placed in the pipeline.
Macleud
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)]
LucD
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
$tcollector
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
LucD
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?
LucD
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’.
Dipak
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”
LucD
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. 🙂
LucD
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?
Bhuvan
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.
LucD
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)
.EXAMPLE
PS> Get-TaskPlus -Alarm $alarm -Details
#>
param(
[CmdletBinding(DefaultParameterSetName=’Filter’)]
[VMware.VimAutomation.ViCore.Impl.V1.VIServerImpl[]]$Server = $global:DefaultVIServer,
[int]$MaxSamples = 100,
[switch]$Details,
[switch]$Keys,
[Parameter(ParameterSetName=’Filter’)]
[VMware.VimAutomation.ViCore.Impl.V1.Alarm.AlarmDefinitionImpl]$Alarm,
[Parameter(ParameterSetName=’Filter’)]
[VMware.VimAutomation.ViCore.Impl.V1.Inventory.InventoryItemImpl]$Entity,
[Parameter(ParameterSetName=’Filter’)]
[switch]$Recurse = $false,
[Parameter(ParameterSetName=’Filter’)]
[VMware.Vim.TaskInfoState[]]$State,
[Parameter(ParameterSetName=’Filter’)]
[DateTime]$Start,
[Parameter(ParameterSetName=’Filter’)]
[DateTime]$Finish,
[Parameter(ParameterSetName=’Filter’)]
[string]$UserName,
[Parameter(ParameterSetName=’Filter’)]
[switch]$Reverse = $false,
[Parameter(ParameterSetName=’Filter’)]
[int]$WindowSize = 100,
[Parameter(ParameterSetName=’RealTime’,Mandatory=$true)]
[switch]$Realtime
)
begin {
function Get-TaskDetails {
param(
[VMware.Vim.TaskInfo[]]$Tasks
)
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) {
$tCollector.ResetCollector()
$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 {
$tCollector.RewindCollector()
$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)
}
$tCollector.DestroyCollector()
}
}
}
}
LucD
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.
Corty
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
LucD
Hi Corty,
Afaik you can not start your own tasks on the vCenter.
Joy Mamer
Luc,,
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 : 10.63.114.15
Error :
Cancelled : N
Reason : User
AlarmName :
AlarmEntity :
ScheduleName :
User : XXXX\jmamer
LucD
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?
Greg
LucD
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 ....
satya
i want to get tasks with respect to datastore
LucD
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
LucD
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())}
matt
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.
LucD
Thanks for noticing that Robert.
I changed it.
Shikhar
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.
LucD
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.
Rusty
Hi,
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?
LucD
Hi Peter,
I’ll have a look
jonpants
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!
IonutN
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:
[VMware.VimAutomation.ViCore.Impl.V1.Inventory.InventoryItemImpl]$Entity,
LucD
Hi,
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
Luc
jack
Hi,
I find a bug.
In the vsphere 5.5, the script is not work.
The result has a fixed time period.
LucD
Thanks for letting me know.
Do you have any more information ?
Is there an error message ?
jrob24
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?
LucD
Are there any messages ?
Did you run that from the PowerCLI prompt ? Or something else ?
jrob24
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
n
+ FullyQualifiedErrorId : ExceptionWhenSetting
LucD
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
jrob24
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
===
if(!$tasks){return}
===
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.
admin
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
===
if(!$tasks){break}
===
LucD
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
#endregion
#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.
$button3_OnClick=
{
#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
}
$vm.ExtensionData.ReconfigVM_Task($spec)
}
}
$button1_OnClick=
{
#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)
}
}
$button2_OnClick=
{
#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
$vm.ReconfigVM($spec)
}
}
$button4_OnClick=
{
#TODO: Place custom script here
}
$OnLoadForm_StateCorrection=
{#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
$button4.add_Click($button4_OnClick)
$form1.Controls.Add($button4)
$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
$button3.add_Click($button3_OnClick)
$form1.Controls.Add($button3)
$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
$button2.add_Click($button2_OnClick)
$form1.Controls.Add($button2)
$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
$button1.add_Click($button1_OnClick)
$form1.Controls.Add($button1)
#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
$form1.add_Load($OnLoadForm_StateCorrection)
#Show the Form
$form1.ShowDialog()| Out-Null
} #End Function
#Call the Function
GenerateForm
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:
if($UserName){
$userNameFilterSpec = New-Object VMware.Vim.TaskFilterSpecByUserName
$userNameFilterSpec.UserList = $UserName
$filter.UserName = $userNameFilterSpec
}
admin
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.
admin
Hi Lenny,
Try something like this
Get-VM | %{
$vmName = $_.Name
Get-HardDisk -VM $_ | where {!$_.ExtensionData.Backing.EagerlyScrub} |
Select @{N="VM";E={$vmName}},Name
}