Events – Part 5 : Powered off for more than 1 week ?

An interesting question was raised in the PowerCLI community by Jörn. He wanted to find all the guests that had been powered off for more than a week.

Before you tackle such a request, it is useful to sit down and think a bit about the solution. If you are going to search through all the events in your vCenter to answer this question, you could be in for a surprise. Depending on the size and activity of your vSphere environment this straight-forward solution could run for hours !

But there is a better way of doing this.

First we can limit the number of guests we have to look at by eliminating all the guests that are currently powered on.

If  we take all the power off events from the last week, we can then remove those  guests from the list we produced in the first step.

And the guests that are still in the list are those that were powered off more than a week ago.

The script that implements this could look like this

Annotations

Line 1: Get a list with the names of all the guests that are currently powered off.

Line 2: Get the events from the last 7 days that were created for a “power off” task.

Line 3: Extract the names of all the guests for which we found a “power off” event.

Line 4: Remove from our list of currently powered off guests all those for which we found a “power off” event in the last week.

Ok, this works. On the console we see all the guests that have been powered off for more than a week.

But still, this script took in a modest vSphere environment 32 minutes to complete.

Isn’t there a faster way ?

There is indeed an important improvement we can make to the script. The Get-VIEvent cmdlet has an -Entity parameter where you tell the cmdlet to only collect events that were fired for one or more specific entities.

If we add the -Entity parameter, the script becomes something like this.

Annotations

Line 1: We collect all the guests as VirtualMachineImpl objects in an array.
Line 2: We extract the names of these guests in a separate variable.
Line 3: We add the -Entity parameter with the array containing all the powered off guests to the Get-VIEvent cmdlet.

And now for the surprise, this script took in the same environment less than 1 minute to complete. That’s more than 32 times faster than the first script (and that without even using any SDK methods !).

Conclusion.

Before you start coding, think about the algorithm you are going to use.

And think about the parameters you can use with the PowerCLI cmdlets.

The time you spend thinking can most of the time be easily recuperated when running the script !

27 Comments

    Joy

    Luc,

    What does this mean?

    PowerCLI C:\VMware_scripts> $events = Get-VIEvent -Start (Get-Date).AddDays(-90) -Entity $vms
    Get-VIEvent : 9/16/2021 9:54:25 AM Get-VIEvent Exception has been thrown by the target of an invocation.
    At line:1 char:11
    + $events = Get-VIEvent -Start (Get-Date).AddDays(-90) -Entity $vms
    + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo : NotSpecified: (:) [Get-VIEvent], VimException
    + FullyQualifiedErrorId : Core_BaseCmdlet_UnknownError,VMware.VimAutomation.ViCore.Cmdlets.Commands.GetEvent

      LucD

      Are you sure there is something in $vms?

    Joy

    Luc,

    Does your base code get the most recent powered off date? For example, what happens if someone powered on the VM for a few days then powered it off again?

    I’m using the number of days as 90. I don’t want to include those VMs that were powered off 90 days ago and were powered on let’s say at 45 days ago then powered off at 30 days ago.

      LucD

      Hi Joy,
      No, this snippet doesn’t only get the most recent power off.

      But if you want to find the VMs that have been powered off 90 days or more ago, it would be a matter of not finding any Power Off events for that VM.
      Something like this for example

      $start = (Get-Date).AddDays(-90)

      Get-VM | where { $_.PowerState -eq 'PoweredOff' } |
      ForEach-Object -Process {
      if (-not (Get-VIEvent -Start $start -Entity $_ | where { $_.FullFormattedMessage -like "*is powered off" })) {
      Write-Host "VM $($_.Name) was last powered off before $($start.ToString())"
      }
      }

      Unless you mean something different?
      Luc

    Ayoub

    HI LUCD ,
    how to tackle this if the VM powered off since a long time ago?

    in this case, the Get-VIEvent command is not useful at all .
    please, could you advise? thank you for all your great effort

      LucD

      Hi,
      I’m afraid there is nothing besides the events and tasks.
      If these are not kept sufficiently long, you’re out of luck with Get-VIEvent I’m afraid.

      There is a possibility to look into the vmware.log file of the VM.
      That should allow you to find the latest timestamp of a poweroff.
      Luc

    Dominic

    Hi lucd,

    I want to get a VM that is powered on more than 2 weeks created by that particular user and sends out email to that user (example, joe@abc.com created a vm named joe-vm and rocky@abc.com created a vm named rocky-vm).

    Kindly need your expertise on this.

    Thanks

      LucD

      Hi Dominic,
      You could try something like this

      $userName = 'joe'

      $targetVM = Get-VM -Name "$($userName)-vm" | where{$_.PowerState -eq 'PoweredOn'} | select -ExpandProperty Name

      if($targetVM){
      $start = (Get-Date).AddDays(-14)
      if(Get-VIEvent -Start $start -MaxSamples ([int]::MaxValue) | where{$_ -is [VMware.Vim.VmPoweredOnEvent] -and $targetVM -contains $_.Vm.Name}){
      Send-MailMessage -Title "VM $($userName)-vm" -Body "VM $(userName)-vm powered on more than 14 days" `
      -From 'lucd@lucd.info' -To "$(userName)@abc.com" -SmtpServer 'mail.abc.com'
      }
      }

      It will find the user’s VM, check if was powered on more than 14 days and if yes, send the user an email.
      Luc

    Natasha

    Is it possible to add who powered off the VM? I’m looking to create a report that lists the VM name, who powered it off, and the date it was powered off.

    Caleb

    One small but significant edit. The original script, line 3 the “start” parameter should be “finish” otherwise you are only searching the last 7 days of logs.

    Org:
    $events = Get-VIEvent -Start (Get-Date).AddDays(-7) -Entity $vms | where{$_.FullFormattedMessage -like “*is powered off”}

    Suggested:
    $events = Get-VIEvent -finish (Get-Date).AddDays(-7) -Entity $vms | where{$_.FullFormattedMessage -like “*is powered off”}

      LucD

      Hi Caleb,
      I think it is correct though, the algorithm I used works as follows:

    1. Get all the VMs that are powered off
    2. Get the VMs that were powered off in the last 7 days
    3. Remove those VMs from the list of all the powered off VMs
    4. The VMs that are left, are those that were powered off more than 7 days ago.

    John

    This is awesome !!!

    One small Qs..

    When we do “Export-Csv “C:\poweredoff-more-than-1-week.csv”…

    Is there any way to add fine name with date & time, what we can do in a shell script eg date

    Also is it possible to add inside the csv any title like “This report is as of XYZ date & time..”

    Thanks in advance !

    kumar

    need to collect more than 60 days powered off virtual machine, when it has been shutdown or powered off ( Exact date and time ) to export CSV or XL format , Kindly help on this, don’t want to remove any vm’s from the vcenter inventory kindly provide scipt for powercli

    Ravi

    need to collect more than 60 days powered off virtual machine, when it has been shutdown or powered off ( Exact date and time ) to export CSV or XL format , Kindly help on this ..

    Jason

    I want to take this output and put it into the body of am email as a report, but I am having trouble getting that to work. any suggestions? Thanks

      LucD

      Hi Jason, the script sends the VirtualMachine objects to the standard output routines, who know how to display this on screen.
      If you want to send it by email, take the last line and change it something like this

      $vmNames = $vms | where {!($lastweekVM -contains $_)} |
      Select Name | Out-String
      Send-Mail -To lucd@lucd.info -From report@lucd.info -Subject "VMs prowered off more than 1 week" -Body $vmName -Server smtp.lucd.info

      Let me know if that works for you ?

    Eugene

    As always your scripts to the rescue. Thanks, this is exactly what I needed.

      LucD

      @Eugene, thanks. Glad you found it useful.

    jithin

    Hi Lucd,

    This script works fine for me..I have one more requrement in this script? is it possible to add the powered off date and time for all the VMs listed?

      LucD

      @Jithin, that is possible but will require a different approach.
      Try it like this

      $finish = (Get-Date).AddDays(-7)
      $vmTab = @{}
      $vms = Get-VM | where {$_.PowerState -eq "PoweredOff"}
      $events = Get-VIEvent -Entity $vms -Finish $finish -MaxSamples ([int]::MaxValue) |
      where{$_.FullFormattedMessage -like "*is powered off"}
      $events | %{
      if($vmTab[$_.Vm.Name] -lt $_.CreatedTime){
      $vmTab[$_.Vm.Name] = $_.CreatedTime
      }
      }
      $vmTab.getEnumerator() | select @{N="VM";E={$_.Key}},@{N="Powered Off";E={$_.Value}}

    Rob

    How can I export the output of this script to a csv ?

    When I export it I do not get the VM Names, I get a list of the vm name length when exported.

    Regards

    Rob

      LucD

      @Rob. The reason for that is that the Export-Csv cmdlet expects an array of objects with properties and the script is producing an array of strings. In that case the Export-Csv cmdlet uses the only propert a string has, it’s length.
      To fix the problem, change the last line as follows

      ...
      $vmPoweredOff | where {!($lastweekVM -contains $_)} | %{
      New-Object PSObject -Property @{VM=$_}
      } | Export-Csv "C:\poweredoff-more-than-1-week.csv" -NoTypeInformation -UseCulture

        Loc Huynh

        You are a legend LucD 🙂
        Thanks you !!!

    Robin

    Your last script doesn’t work for me.
    I connect to my virtualcenter, point it at a particular folder and it simply outputs all vms in the Powered Off state regardless of what task I search for or if it is or is not present in the last 7 days.

    I would like it to show me vms that do not contain the Power On task in the last 60 days if possible.

    Any help would be appreciated, this script would help a lot with vm sprawl.

    Thanks.

      LucD

      Robin,
      You’re right, there is a bug in the script.
      The message “Task: Power off*” only appears when you do a “hard” power off.
      When you do a “Shut down guest“, that message does not appear.
      The common message between both types of power off is “ vm-name on esx-name in datacenter-name is powered off“.
      I should have eaten my own cake and should have done a bit of thinking before I submitted the post ;-).
      Both scripts have been updated.
      Can you give it another try ?

      To look 60 days back, just change Start parameter on the Get-ViEvent cmdlet to:
      -Start (Get-Date).AddDays(-60).

      Sorry again about the inconvenience.
      Luc.

        Joy Mamer

        Luc,
        I wanted to incoporate resources used.

        $VMs = get-vm |Where-object {$_.powerstate -eq “poweredoff”}

        $Datastores = Get-Datastore | select Name, Id

        Get-VIEvent -Start (Get-Date).AddDays(-180) | where{$_.FullFormattedMessage -like “*is powered off”} |

        Group-Object -Property {$_.Vm.Name} | %{

        $lastPO = $_.Group | Sort-Object -Property CreatedTime -Descending | Select -First 1

        $vm = Get-VIObjectByVIView -MORef $_.Group[0].VM.VM

        $row = ” | select VMName,Powerstate,OS,Host,Cluster,Datastore,NumCPU,MemMb,DiskGb,PowerOFF

        $row.VMName = $vm.Name

        $row.Powerstate = $vm.Powerstate

        $row.OS = $vm.Guest.OSFullName

        $row.Host = $vm.VMHost.name

        $row.Cluster = $vm.VMHost.Parent.Name

        $row.Datastore = $Datastores | Where{$_.Id -eq ($vm.DatastoreIdList | select -First 1)} | Select -ExpandProperty Name

        $row.NumCPU = $vm.NumCPU

        $row.MemMb = $vm.MemoryMB

        $row.DiskGb = Get-HardDisk -VM $vm | Measure-Object -Property CapacityGB -Sum | select -ExpandProperty Sum

        $row.PowerOFF = $lastPO.CreatedTime

        $report += $row

        }

        My script gave me a zero byte file // failed.

          LucD

          Hi Joy,
          Did you declare the $report array?
          And you have to send the content to the output after the loop.

          Something like this for example

          $VMs = Get-VM | Where-Object { $_.powerstate -eq "poweredoff" }
          $Datastores = Get-Datastore | Select-Object Name, Id

          $report = @()

          Get-VIEvent -Start (Get-Date).AddDays(-180) | Where-Object { $_.FullFormattedMessage -like "*is powered off" } |
          Group-Object -Property { $_.Vm.Name } | ForEach-Object {
          $lastPO = $_.Group | Sort-Object -Property CreatedTime -Descending | Select-Object -First 1
          $vm = Get-VIObjectByVIView -MORef $_.Group[0].VM.VM
          $row = '' | Select-Object VMName, Powerstate, OS, Host, Cluster, Datastore, NumCPU, MemMb, DiskGb, PowerOFF
          $row.VMName = $vm.Name
          $row.Powerstate = $vm.Powerstate
          $row.OS = $vm.Guest.OSFullName
          $row.Host = $vm.VMHost.name
          $row.Cluster = $vm.VMHost.Parent.Name
          $row.Datastore = $Datastores | Where-Object { $_.Id -eq ($vm.DatastoreIdList | Select-Object -First 1) } | Select-Object -ExpandProperty Name
          $row.NumCPU = $vm.NumCPU
          $row.MemMb = $vm.MemoryMB
          $row.DiskGb = Get-HardDisk -VM $vm | Measure-Object -Property CapacityGB -Sum | Select-Object -ExpandProperty Sum
          $row.PowerOFF = $lastPO.CreatedTime
          $report += $row
          }
          $report

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.