2

What I'm doing is so simple that I'm struggling to find an answer for it. I'm trying to subtract two equal length arrays from one another

$free_array = get-wmiobject -class win32_logicaldisk | select -ExpandProperty freespace
$size_array = get-wmiobject -class win32_logicaldisk | select -ExpandProperty size

ForEach ($size in $size_array)
  {
    Write-Host Statistic: $size - $freespace
  }

mklement0
  • 382,024
  • 64
  • 607
  • 775
wetin
  • 360
  • 1
  • 3
  • 13

2 Answers2

6

I don't think that PowerShell has a built-in function to map across multiple arrays simultaneously and so instead you can use a range operator and then index into both arrays as needed:

foreach ($Index in (0..($free_array.Count - 1))) {
    Write-Host Statistic: ($size_array[$Index] - $free_array[$Index])
}

However, your task could also be done like this which I think would be more readable:

$LogicalDisks = Get-CimInstance -ClassName Win32_LogicalDisk

foreach ($LogicalDisk in $LogicalDisks) {
    Write-Host Statistic: ($LogicalDisk.Size - $LogicalDisk.FreeSpace)
}
Don Cruickshank
  • 5,641
  • 6
  • 48
  • 48
2

To complement Don Cruickshank's helpful answer:

As an aside: Below I'm using Get-CimInstance rather than Get-WmiObject, because 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) 7+, where all future effort will go, doesn't even have them anymore. For more information, see this answer.

If you can operate on a single collection, you don't even need a foreach loop and can use a single pipeline with a call to ForEach-Object:

Get-CimInstance win32_logicaldisk | ForEach-Object {
  "Statistic: " + ($_.Size - $_.FreeSpace)
}

As for enumerating multiple collections in parallel (without the use of indices):

[Linq.Enumerable]::Zip() can do that for two collections:

# Two sample arrays to enumerate in parallel:
[string[]] $a1 = 'one', 'two', 'three'
[int[]] $a2 = 1, 2, 3

foreach ($tuple in [Linq.Enumerable]::Zip($a1, $a2)) {
  '{0}: {1}' -f $tuple[0], $tuple[1]
}

Note: In earlier PowerShell (Core) versions and in Windows PowerShell you'll have to use .Item1 / .Item2 instead of [0] / [1]

However, as the above shows, this is a somewhat obscure solution that isn't PowerShell-idiomatic, because PowerShell doesn't support .NET extension methods and requires strong typing of the input collections to enable PowerShell to enable PowerShell to infer the specific types the generic method should be called with.

Also, with differently sized input collections, enumeration stops once the smaller collection has run out of items.


[Was declined] GitHub proposal #14732 suggests introducing a PowerShell-idiomatic feature that additionally supports more than just 2 collections:

  • Update: The proposal has since been rejected.
# Two sample arrays to enumerate in parallel:
$a1 = 'one', 'two', 'three'
$a2 = 1, 2, 3

# WISHFUL THINKING, as of PowerShell 7.2
foreach ($a1Element, $a2Element in $a1, $a2) {
  '{0}: {1}' -f $a1Element, $a2Element
}

If this feature were implemented, the above would output:

one: 1
two: 2
three: 3
mklement0
  • 382,024
  • 64
  • 607
  • 775