I found my way here in 2024 looking for a (somewhat) platform-agnostic solution.
I needed something that would work on both Windows & Linux, and I needed it to always give me the same object 'type'/'shape'. Obviously WMI isn't an option, and I probably wouldn't use it even if it was.
For anyone else looking for something similar, I threw together this quick function. It uses free
on Linux and systeminfo
on Windows, as suggested elsewhere in this thread.
I doubt it will work on anything other than Windows/Linux, although you're welcome to try.
function Get-AvailableMemory
{
[CmdletBinding()]
param()
$Script:PSNativeCommandUseErrorActionPreference = $true
$spaceUnit = New-Object PSObject -Property ([ordered]@{
'Bytes' = [ulong]0
}) |
Add-Member -MemberType ScriptProperty -Name 'Kibibytes' -Value { [double]($this.Bytes / 1Kb) } -SecondValue { $this.Bytes = [ulong]($args[0] * 1Kb) } -Force -PassThru |
Add-Member -MemberType ScriptProperty -Name 'Kilobytes' -Value { [double]($this.Bytes / 1000) } -SecondValue { $this.Bytes = [ulong]($args[0] * 1000) } -Force -PassThru |
Add-Member -MemberType ScriptProperty -Name 'Mebibytes' -Value { [double]($this.Bytes / 1Mb) } -SecondValue { $this.Bytes = [ulong]($args[0] * 1Mb) } -Force -PassThru |
Add-Member -MemberType ScriptProperty -Name 'Megabytes' -Value { [double]($this.Bytes / 1000000) } -SecondValue { $this.Bytes = [ulong]($args[0] * 1000000) } -Force -PassThru |
Add-Member -MemberType ScriptProperty -Name 'Gibibytes' -Value { [double]($this.Bytes / 1Gb) } -SecondValue { $this.Bytes = [ulong]($args[0] * 1Gb) } -Force -PassThru |
Add-Member -MemberType ScriptProperty -Name 'Gigabytes' -Value { [double]($this.Bytes / 1000000000) } -SecondValue { $this.Bytes = [ulong]($args[0] * 1000000000) } -Force -PassThru |
Add-Member -MemberType ScriptMethod -Name 'ToString' -Value { $this.Mebibytes.ToString('0,0 "MiB"') } -Force -PassThru
$result = New-Object PSObject -Property ([ordered]@{
'Physical' = New-Object PSObject -Property ([ordered]@{
'Total' = $spaceUnit.PSObject.Copy()
'Used' = $spaceUnit.PSObject.Copy()
'Free' = $spaceUnit.PSObject.Copy()
}) | Add-Member -MemberType ScriptMethod -Name 'ToString' -Value { ('{0}/{1} MiB' -f $this.Free.Mebibytes.ToString('0,0'), $this.Total.Mebibytes.ToString('0,0')) } -Force -PassThru
'Virtual' = New-Object PSObject -Property ([ordered]@{
'Total' = $spaceUnit.PSObject.Copy()
'Used' = $spaceUnit.PSObject.Copy()
'Free' = $spaceUnit.PSObject.Copy()
}) | Add-Member -MemberType ScriptMethod -Name 'ToString' -Value { ('{0}/{1} MiB' -f $this.Free.Mebibytes.ToString('0,0'), $this.Total.Mebibytes.ToString('0,0')) } -Force -PassThru
})
if ($Global:IsLinux)
{
$free = ((Invoke-Expression 'free -b') -replace '^\w+:') -replace '[\s\t]+', ',' | ConvertFrom-Csv -WarningAction SilentlyContinue
$result.Physical.Total.Bytes = [ulong]::Parse($free[0].total)
$result.Physical.Used.Bytes = [ulong]::Parse($free[0].used)
$result.Physical.Free.Bytes = [ulong]::Parse($free[0].free)
$result.Virtual.Total.Bytes = [ulong]::Parse($free[1].total)
$result.Virtual.Used.Bytes = [ulong]::Parse($free[1].used)
$result.Virtual.Free.Bytes = [ulong]::Parse($free[1].free)
}
elseif ($Global:IsWindows)
{
if (!(Test-Path 'C:\windows\system32\systeminfo.exe'))
{
throw New-Object System.NotSupportedException("systeminfo.exe couldn't be found")
}
# The redirection prevents systeminfo writing its current status to the error stream. You can remove it if you'd rather.
$infoRaw = Invoke-Expression 'systeminfo.exe /FO CSV 2> $null'
$info = $infoRaw[-1] | ConvertFrom-Csv -Header ($infoRaw[0] -replace '[\s:]').Split(',', [System.StringSplitOptions]::RemoveEmptyEntries -bor [System.StringSplitOptions]::TrimEntries).Trim('"')
$numberFormat = New-Object System.Globalization.NumberFormatInfo -Property @{'CurrencySymbol' = 'MB'}
$numberStyle = [System.Globalization.NumberStyles]::Currency
$result.Physical.Total.Mebibytes = [int]::Parse($info.TotalPhysicalMemory, $numberStyle, $numberFormat)
$result.Physical.Used.Mebibytes = ([int]::Parse($info.TotalPhysicalMemory, $numberStyle, $numberFormat) - [int]::Parse($info.AvailablePhysicalMemory, $numberStyle, $numberFormat))
$result.Physical.Free.Mebibytes = [int]::Parse($info.AvailablePhysicalMemory, $numberStyle, $numberFormat)
$result.Virtual.Total.Mebibytes = [int]::Parse($info.VirtualMemoryMaxSize, $numberStyle, $numberFormat)
$result.Virtual.Used.Mebibytes = [int]::Parse($info.VirtualMemoryInUse, $numberStyle, $numberFormat)
$result.Virtual.Free.Mebibytes = [int]::Parse($info.VirtualMemoryAvailable, $numberStyle, $numberFormat)
}
else
{
throw New-Object System.NotSupportedException('This command only supports Windows and Linux')
}
return $result
}