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
Categories: