PowerShell: Get ‘Size on Disk’ of Files in Windows

There are many methods of attempting to obtain “size on disk” values of files in Windows. Power shell can be used to load WinAPIEx, call _WinAPI_GetFileSizeOnDisk, Or overload shell sessionS with functions such as [Win32Functions.ExtendedFileInfo]::GetFileSizeOnDisk($file). Unfortunately, Microsoft has not discloseD the exact method That Explorer.exe Used to calculate these values. Thus, this is my effort in guessing ‘size on disk’ values of folders.

add-type -type  @'
using System;
using System.Runtime.InteropServices;
using System.ComponentModel;
using System.IO;

namespace Win32Storage
{
public class ExtendedFileInfo
{
public static long GetFileSizeOnDisk(string file)
{
FileInfo info = new FileInfo(file);
uint dummy, sectorsPerCluster, bytesPerSector;
int result = GetDiskFreeSpaceW(info.Directory.Root.FullName, out sectorsPerCluster, out bytesPerSector, out dummy, out dummy);
if (result == 0) throw new Win32Exception();
uint clusterSize = sectorsPerCluster * bytesPerSector;
uint hosize;
uint losize = GetCompressedFileSizeW(file, out hosize);
long size;
size = (long)hosize << 32 | losize;
return size;
}

[DllImport("kernel32.dll")]
static extern uint GetCompressedFileSizeW([In, MarshalAs(UnmanagedType.LPWStr)] string lpFileName,
[Out, MarshalAs(UnmanagedType.U4)] out uint lpFileSizeHigh);

[DllImport("kernel32.dll", SetLastError = true, PreserveSig = true)]
static extern int GetDiskFreeSpaceW([In, MarshalAs(UnmanagedType.LPWStr)] string lpRootPathName,
out uint lpSectorsPerCluster, out uint lpBytesPerSector, out uint lpNumberOfFreeClusters,
out uint lpTotalNumberOfClusters);
}
}
'@

function getFolderSizeOnDisk{
param(
[string]$folder="C:\Program Files (x86)",
$clusterSize=4096
)

if(!($clusterSize)){$clusterSize=(Get-WmiObject -Class Win32_Volume | Where-Object {$_.Name -eq $volumeName}).BlockSize;}
$sum=0

function getFileSizeOnDisk{
param(
[int32]$clusterSize,
[string]$file="C:\Windows\system32\cmd.exe",
$maximumSizeThreshold=604
)
# At this time, it's unclear as to what minimum size a file should be so that Windows would compact it into NTFS MFT record,
# so that there's no wastage of 1 cluster extent;
# The range of values seem to be from 539 to 604 bytes
[int]$minimumSizeThreshold=539
#[Double]$size=$(try{get-item "$file"}catch{}).Length
[Double]$size=[Win32Storage.ExtendedFileInfo]::GetFileSizeOnDisk($file)
[int]$sizeOnDisk=0;

if ($size -le $minimumSizeThreshold){
$sizeOnDisk=0;
}else{
if ($size -lt $maximumSizeThreshold){
$sizeOnDisk=(($size-$minimumSizeThreshold)/($maximumSizeThreshold-$minimumSizeThreshold))*$clusterSize
} # This is a blind guess probability that file on disk is equal to cluster size
else{
[int]$remainder=$size%$clusterSize
if ($remainder -eq 0){
$sizeOnDisk=$size
}else{
$sizeOnDisk=$size-$remainder+$clusterSize;
}
}
}

#if($size -ge 538 -and $size -lt 542){write-host $file}
return $sizeOnDisk
}

Get-ChildItem $folder -Recurse -ea SilentlyContinue | where { !$_.PSisContainer } | %{$sizeOnDisk=getFileSizeOnDisk -clusterSize $clusterSize -file $_.FullName;$sum+=$sizeOnDisk}
return $sum;
}
getFolderSizeOnDisk -folder "c:\program files"

Usage

PS C:\Windows\system32> getFolderSizeOnDisk -folder "c:\program files"
3789603190

performance:

PS C:\Users\Wala> measure-command{getFolderSizeOnDisk -folder "c:\program files"}
Days : 0
Hours : 0
Minutes : 0
Seconds : 11
Milliseconds : 533
Ticks : 115334537
TotalDays : 0.000133489047453704
TotalHours : 0.00320373713888889
TotalMinutes : 0.192224228333333
TotalSeconds : 11.5334537
TotalMilliseconds : 11533.4537

This version is pure PowerShell (no overloading the current session with additional dlls)

function getFileSizeOnDisk{
param(
[int32]$clusterSize,
[string]$file="C:\Windows\system32\cmd.exe",
[int]$maximumSizeThreshold=604
)
# At this time, it's unclear as to what minimum size a file should be so that Windows would compact it into NTFS MFT record, so that there's no wastage of 1 cluster extent;
# The range of values seem to be from 539 to 565 bytes
[int]$minimumSizeThreshold=539
[Double]$size=$(try{get-item "$file"}catch{}).Length
[int]$sizeOnDisk=0;

if ($size -le $minimumSizeThreshold){
$sizeOnDisk=0;
}else{
if ($size -lt $maximumSizeThreshold){
$sizeOnDisk=(($size-$minimumSizeThreshold)/($maximumSizeThreshold-$minimumSizeThreshold))*$clusterSize
} # This is a blind guess probability that file on disk is equal to cluster size
else{
[int]$remainder=$size%$clusterSize
if ($remainder -eq 0){
$sizeOnDisk=$size
}else{
$sizeOnDisk=$size-$remainder+$clusterSize;
}
}
}
return $sizeOnDisk
}

#getFileSizeOnDisk -clusterSize 4096 -file C:\Temp\Windows10Debloater-master\LICENSE

function getFolderSizeOnDisk{
param(
[string]$folder="C:\Program Files"
)
$clusterSize=4096
if(!($clusterSize)){$clusterSize=(Get-WmiObject -Class Win32_Volume | Where-Object {$_.Name -eq $volumeName}).BlockSize;}
$sum=0
Get-ChildItem $folder -Recurse -ea SilentlyContinue | where { !$_.PSisContainer } | %{$sizeOnDisk=getFileSizeOnDisk -clusterSize $clusterSize -file $_.FullName;$sum+=$sizeOnDisk}
return $sum;
}

getFolderSizeOnDisk

However, the function above is slower as shown below. Hence, overloading session with extra dependencies is the way to go.

PS C:\Users\Wala> measure-command{getFolderSizeOnDisk}
Days : 0
Hours : 0
Minutes : 0
Seconds : 21
Milliseconds : 996
Ticks : 219967486
TotalDays : 0.000254591997685185
TotalHours : 0.00611020794444444
TotalMinutes : 0.366612476666667
TotalSeconds : 21.9967486
TotalMilliseconds : 21996.7486

Leave a Reply

Your email address will not be published. Required fields are marked *