1

I have a script that works fine on some machines,(all machines have same,5.1 Powershell version)

Script calculates CPU usage percentage for windows services

$cpu   = Get-WmiObject –class Win32_processor -Property NumberOfCores, NumberOfLogicalProcessors
     # Define the number of top services to display
     $services = Get-WmiObject Win32_Service | Where-Object { $_.State -eq 'Running' } | Sort-Object -Property ProcessId | ForEach-Object {
        $process = Get-Process -Id $_.ProcessId
        if ($process.StartTime -gt 0){
           $ts = (New-TimeSpan -Start $process.StartTime -ea si).TotalSeconds
           $cpuUsagePercentage = [math]::Round($process.CPU *100 / $ts /  $cpu.numberoflogicalprocessors, 2)
        }
        else{
           $cpuUsagePercent = 0
        }
       
        $_ | Add-Member -MemberType NoteProperty -Name "CpuUsage" -Value $cpuUsagePercentage
        $_
     } | Sort-Object -Property CpuUsage -Descending | Select-Object Name, ProcessId, CpuUsage -First 3

Script fails on this line with error from the title [System.Object[]] does not contain a method named 'op_Division'

$cpuUsagePercentage = [math]::Round($process.CPU *100 / $ts /  $cpu.numberoflogicalprocessors, 2)

I debugged this and put breakpoint on above line and values are:

$process.CPU=699
$ts=737146,1126995
$cpu.numberoflogicalprocessors=1

No idea why it works on some machine while it fails on other

$cpu variable:

__GENUS                   : 2
__CLASS                   : Win32_Processor
__SUPERCLASS              :
__DYNASTY                 :
__RELPATH                 :
__PROPERTY_COUNT          : 2
__DERIVATION              : {}
__SERVER                  :
__NAMESPACE               :
__PATH                    :
NumberOfCores             : 1
NumberOfLogicalProcessors : 1
PSComputerName            :

__GENUS                   : 2
__CLASS                   : Win32_Processor
__SUPERCLASS              :
__DYNASTY                 :
__RELPATH                 :
__PROPERTY_COUNT          : 2
__DERIVATION              : {}
__SERVER                  :
__NAMESPACE               :
__PATH                    :
NumberOfCores             : 1
NumberOfLogicalProcessors : 1
PSComputerName            :

__GENUS                   : 2
__CLASS                   : Win32_Processor
__SUPERCLASS              :
__DYNASTY                 :
__RELPATH                 :
__PROPERTY_COUNT          : 2
__DERIVATION              : {}
__SERVER                  :
__NAMESPACE               :
__PATH                    :
NumberOfCores             : 1
NumberOfLogicalProcessors : 1
PSComputerName            :

__GENUS                   : 2
__CLASS                   : Win32_Processor
__SUPERCLASS              :
__DYNASTY                 :
__RELPATH                 :
__PROPERTY_COUNT          : 2
__DERIVATION              : {}
__SERVER                  :
__NAMESPACE               :
__PATH                    :
NumberOfCores             : 1
NumberOfLogicalProcessors : 1
PSComputerName            :

__GENUS                   : 2
__CLASS                   : Win32_Processor
__SUPERCLASS              :
__DYNASTY                 :
__RELPATH                 :
__PROPERTY_COUNT          : 2
__DERIVATION              : {}
__SERVER                  :
__NAMESPACE               :
__PATH                    :
NumberOfCores             : 1
NumberOfLogicalProcessors : 1
PSComputerName            :

__GENUS                   : 2
__CLASS                   : Win32_Processor
__SUPERCLASS              :
__DYNASTY                 :
__RELPATH                 :
__PROPERTY_COUNT          : 2
__DERIVATION              : {}
__SERVER                  :
__NAMESPACE               :
__PATH                    :
NumberOfCores             : 1
NumberOfLogicalProcessors : 1
PSComputerName            :
overflowed
  • 1,095
  • 1
  • 18
  • 39

2 Answers2

1

Indeed the problem is that you cannot apply / (the division operator) to array operands, and that is what the error message is trying to express (try 2 / @(1, 2) or @(1, 2) / 2 to provoke the error), and the solution is to use single numbers only.

To complement your own effective solution with simpler alternatives, using Get-CimInstance in lieu of the (soft-)deprecated Get-WmiObject cmdlet[1] - this will return objects of a different type, but with more information (properties) about your CPUs.

How to extract only the first object from a command's output:
  • With Select-Object, in the pipeline:

    $cpu = 
      Get-CimInstance –class Win32_processor -Property NumberOfCores, NumberOfLogicalProcessors |
      Select-Object -First 1
    
  • With an array index expression, applied to an array subexpression (@(...)):

    # Note: (...) will often do instead of @(...), but the latter is more robust.
    $cpu = @(
      Get-CimInstance –class Win32_processor -Property NumberOfCores, NumberOfLogicalProcessors
    )[0]
    

Note:

  • While your particular Get-WmiObject / Get-CimInstance by definition returns at least 1 object (unless a catastrophic failure occurs), both solutions above can result in no output in principle, but there's a subtle distinction.

    • An index expression such as @(...)[0] will return $null if no element exists at the specified index.

      • Caveat: If the command has no output, this will actually fail if Set-StrictMode is set to -Version 3 or above (by default, no strict mode is in effect).
    • By contrast, if a command such as Select-Object produces no output, what is technically returned is an "enumerable null" ("Automation null"), a special singleton value that behaves like $null in expression contexts, but behaves like an empty collection (enumerable) in enumeration contexts, such as the pipeline.

    • This distinction typically doesn't matter, but can - see this answer for details.

  • There's also a difference with respect output timing and output collection:

    • As a command that operates in PowerShell's pipeline, Select-Object receives and outputs objects one by one, a process known as streaming.

      • That is, as soon as Select-Object receives the first input object emitted by Get-CimInstance, it emits it, and then terminates the Get-CimInstance call (as well as any other so-called upstream commands in the same pipeline) given that only one object is needed.
    • By contrast, @(...)[0], due to the indexing being an expression, requires collecting the output from ... in full, up front in memory, as an array (of type [object[]]), to which index [0] is applied.

      • That is, Get-CimInstance must run to completion, and all its output objects must be collected first, before the first object is extracted.

      • A note on using array indexing with @(...), the array subexpression operator vs. just the (...), the grouping operator:

        • @(...) acts like an array "guarantor": If the output from ... happens to be a single object, PowerShell wraps it in a single-element array, so you are guaranteed to be dealing with an array.

        • While PowerShell's unified handling of scalars and arrays - see this answer - often makes this unnecessary (e.g., (42)[0] yields 42, just like @(42)[0]), there are exceptions, which makes @(...)[0] the only robust choice:

          • If a single object itself has an indexer - which is notably true for strings ([string]), using (...)[0] will invoke that object's indexer instead (e.g., while @(Write-Output foo)[0] is 'foo', (Write-Output foo)[0] is f(!), because the expression is equivalent to 'foo'[0])
          • It isn't always obvious which objects have their own indexers (e.g, [xml], though a numeric index should arguably still work - see GitHub issue #11923), and other factors too can interfere with PowerShell's "scalar indexing"; case in point: applying (...)[0] to your Get-WmiObject call yields no output (whereas it does work with Get-CimInstance).
    • Tradeoffs:

      • While using array indexing is more concise and often performs better (not in this case), it comes at the expense of increased memory consumption and potentially delayed to-display output.

      • Streaming output can keep memory use constant (assuming that output isn't collected in full afterwards) or use less memory (if the collected output amounts to a filtered subset of the input data), but is generally slower, due to the overhead of one-by-one processing.


[1] The CIM cmdlets (e.g., Get-CimInstance) superseded the WMI cmdlets (e.g., Get-WmiObject) in PowerShell v3 (released in September 2012). Therefore, the WMI cmdlets should be avoided, not least because PowerShell (Core) v6+, where all future effort will go, doesn't even have them anymore. Note that WMI still underlies the CIM cmdlets, however. For more information, see this answer.

mklement0
  • 382,024
  • 64
  • 607
  • 775
0

thanks to @Santiago Squarzon's suggestion, added a check if $cpu variable is array, if yes, select first element.Noticed that for every element of array, getting same value for NumberoOfLogicalProcessors

$cpu   = Get-WmiObject –class Win32_processor -Property NumberOfCores, NumberOfLogicalProcessors
     if ($cpu -is[array]){
       $cpu = $cpu[0]
     }
# rest of the code is same
overflowed
  • 1,095
  • 1
  • 18
  • 39