89
var length = new System.IO.FileInfo(path).Length;

This gives the logical size of the file, not the size on the disk.

I wish to get the size of a file on the disk in C# (preferably without interop) as would be reported by Windows Explorer.

It should give the correct size, including for:

  • A compressed file
  • A sparse file
  • A fragmented file
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Wernight
  • 36,122
  • 25
  • 118
  • 131

4 Answers4

55

This uses GetCompressedFileSize, as ho1 suggested, as well as GetDiskFreeSpace, as PaulStack suggested, it does, however, use P/Invoke. I have tested it only for compressed files, and I suspect it does not work for fragmented files.

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 + clusterSize - 1) / clusterSize) * clusterSize;
}

[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);
Wai Ha Lee
  • 8,598
  • 83
  • 57
  • 92
margnus1
  • 1,064
  • 7
  • 7
  • are you sure this is correct if (result == 0) throw new Win32Exception(result); – Simon Aug 30 '11 at 06:21
  • The 'if (result == 0)' bit is correct (see [msdn](http://msdn.microsoft.com/en-us/library/aa364935.aspx)), but you're right that I am using the wrong constructor. I will fix it now. – margnus1 Sep 01 '11 at 19:54
  • `FileInfo.Directory.Root` doesn't look as if it could handle any kind of filesystem links. So it only works on classic local drive letters with no symlinks/hardlinks/junction points or whatever NTFS has to offer. – ygoe Aug 24 '13 at 18:11
  • Could any one please give step by step explanation, what has been done at different steps? It will be very helpful to understand, how it actually works.Thanks. – bapi Nov 13 '13 at 12:31
  • 5
    This code requires the namespaces `System.ComponentModel` and `System.Runtime.InteropServices`. – Kenny Evitt Aug 16 '14 at 22:35
  • Found a weird bug with this method in the `user\appdata\local\microsoft\windowsapps\Microsoft.DesktopAppInstaller_somekey\...` folder. All files there show 0 disk size in Windows explorer and yet they show a big size with this method. – Daniel Möller Oct 27 '22 at 14:47
  • This answer was written when .NET was only officially supported on Windows. Not that Microsoft releases .NET SDK/Runtime for Linux, etc, this method is not universal. I guess on Linux, we have to call Linux API's, so things are a lot complicated to do manually. Isn't there some sort of library for this? – Damn Vegetables Nov 23 '22 at 13:58
  • I tested this with my code. But it gives same value for every file in the folder. @margnus1 Do you know the reason? – Kalpani Ranasinghe Feb 14 '23 at 13:25
17

The code above does not work properly on Windows Server 2008 or 2008 R2 or Windows 7 and Windows Vista based systems as cluster size is always zero (GetDiskFreeSpaceW and GetDiskFreeSpace return -1 even with UAC disabled.) Here is the modified code that works.

C#

public static long GetFileSizeOnDisk(string file)
{
    FileInfo info = new FileInfo(file);
    uint clusterSize;
    using(var searcher = new ManagementObjectSearcher("select BlockSize,NumberOfBlocks from Win32_Volume WHERE DriveLetter = '" + info.Directory.Root.FullName.TrimEnd('\\') + "'") {
        clusterSize = (uint)(((ManagementObject)(searcher.Get().First()))["BlockSize"]);
    }
    uint hosize;
    uint losize = GetCompressedFileSizeW(file, out hosize);
    long size;
    size = (long)hosize << 32 | losize;
    return ((size + clusterSize - 1) / clusterSize) * clusterSize;
}

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

VB.NET

  Private Function GetFileSizeOnDisk(file As String) As Decimal
        Dim info As New FileInfo(file)
        Dim blockSize As UInt64 = 0
        Dim clusterSize As UInteger
        Dim searcher As New ManagementObjectSearcher( _
          "select BlockSize,NumberOfBlocks from Win32_Volume WHERE DriveLetter = '" + _
          info.Directory.Root.FullName.TrimEnd("\") + _
          "'")

        For Each vi As ManagementObject In searcher.[Get]()
            blockSize = vi("BlockSize")
            Exit For
        Next
        searcher.Dispose()
        clusterSize = blockSize
        Dim hosize As UInteger
        Dim losize As UInteger = GetCompressedFileSizeW(file, hosize)
        Dim size As Long
        size = CLng(hosize) << 32 Or losize
        Dim bytes As Decimal = ((size + clusterSize - 1) / clusterSize) * clusterSize

        Return CDec(bytes) / 1024
    End Function

    <DllImport("kernel32.dll")> _
    Private Shared Function GetCompressedFileSizeW( _
        <[In](), MarshalAs(UnmanagedType.LPWStr)> lpFileName As String, _
        <Out(), MarshalAs(UnmanagedType.U4)> lpFileSizeHigh As UInteger) _
        As UInteger
    End Function
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Steve Johnson
  • 3,054
  • 7
  • 46
  • 71
  • System.Managment reference is required for this code to work. It appears as if there is no standard way of getting cluster size accurately on Windows (6.x versions) except WMI. :| – Steve Johnson Jul 29 '11 at 05:14
  • 1
    I wrote my code on a Vista x64 machine and now tested it on a W7 x64 machine in 64-bit and WOW64 mode. Note that GetDiskFreeSpace is [supposed](http://msdn.microsoft.com/en-us/library/aa364935.aspx) to return nonzero on _success_. – margnus1 Sep 01 '11 at 20:23
  • 1
    Original question asks for C# – Shane Courtrille Jan 21 '13 at 20:27
  • 5
    This code doesn't even compile (one closing parenthesis missing on the using) and the one liner is very awful for learning purposes – Mickael V. Apr 12 '17 at 15:33
  • 1
    This code also has a compile issue when requesting `.First()` as it is an `IEnumerable` and not an `IEnumerable`, if you want to use the code first call `.Cast()` – yoel halb Mar 08 '18 at 15:58
5

According to MSDN social forums:

The size on disk should be the sum of the size of the clusters that store the file:
long sizeondisk = clustersize * ((filelength + clustersize - 1) / clustersize);
You'll need to dip into P/Invoke to find the cluster size; GetDiskFreeSpace() returns it.

See How to get the size on disk of a file in C#.

But please note the point that this will not work in NTFS where compression is switched on.

CarenRose
  • 1,266
  • 1
  • 12
  • 24
stack72
  • 8,198
  • 1
  • 31
  • 35
  • 2
    I suggest using something like `GetCompressedFileSize` rather than `filelength` to account for compressed and/or sparse files. – Hans Olsson Sep 20 '10 at 11:06
-3

I think it will be like this:

double ifileLength = (finfo.Length / 1048576); //return file size in MB ....

I'm still doing some testing for this, to get a confirmation.

Ondrej Janacek
  • 12,486
  • 14
  • 59
  • 93
bapi
  • 1,903
  • 10
  • 34
  • 59
  • 8
    This is the size the file (number of bytes within the file). Depending on block sizes of the actual hardware, a file might consume more disk space. E.g. a file of 600byte on my HDD used 4kB on disk. So this answer is incorrect. – 0xBADF00D May 25 '16 at 08:51