Get size of file on disk
Asked Answered
T

4

90
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
Throughout answered 20/9, 2010 at 10:37 Comment(0)
G
57

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);
Grekin answered 20/9, 2010 at 11:45 Comment(8)
are you sure this is correct if (result == 0) throw new Win32Exception(result);Abarca
The 'if (result == 0)' bit is correct (see msdn), but you're right that I am using the wrong constructor. I will fix it now.Grekin
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.Bunder
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.Dilly
This code requires the namespaces System.ComponentModel and System.Runtime.InteropServices.Sclerosis
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.Trichinopoly
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?Conformist
I tested this with my code. But it gives same value for every file in the folder. @Grekin Do you know the reason?Bhang
S
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
Scrivens answered 29/7, 2011 at 4:58 Comment(5)
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. :|Scrivens
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 to return nonzero on success.Grekin
Original question asks for C#Cadmus
This code doesn't even compile (one closing parenthesis missing on the using) and the one liner is very awful for learning purposesPernell
This code also has a compile issue when requesting .First() as it is an IEnumerable and not an IEnumerable<T>, if you want to use the code first call .Cast<object>()Berga
T
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.

Tittle answered 20/9, 2010 at 10:47 Comment(1)
I suggest using something like GetCompressedFileSize rather than filelength to account for compressed and/or sparse files.Deidradeidre
D
-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.

Dilly answered 13/11, 2013 at 13:39 Comment(1)
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.Carbonado

© 2022 - 2024 — McMap. All rights reserved.