Calculate a file's size on disk with C#

elorc
elorc used Ask the Experts™
on
I'm trying to calculate a file's size on disk (not simply the file size), but it doesn't seem to be working accurately. I initially used a method described on the MSDN forums, which involved using GetDiskFreeSpace to return cluster size. I took the uint value of lpBytesPerSector and performed the following calculation:

uint TotalSize = lpBytesPerSector * ((FileLength + lpBytesPerSector - 1) / lpBytesPerSector)

Open in new window


The result is inaccurate, however. I run this calculation on all of the files in a particular folder, and it returns a value of 3,491,840 bytes. Windows Explorer indicates that the folder's size on disk is 21,057,536 bytes.

Is there a better or more accurate way to perform this calculation?
Comment
Watch Question

Do more with

Expert Office
EXPERT OFFICE® is a registered trademark of EXPERTS EXCHANGE®
Most Valuable Expert 2012
Top Expert 2008
Commented:
Is this file compressed, or uncompressed?  I have some WMI code, but it returns size on disk, not physical size.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Management;
using System.Runtime.InteropServices;

namespace CSharp.CodeSnippet.WMI
{

    public class Win32_Volume : BaseWmiObject<Win32_Volume>, IWmiObject
    {

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

        public decimal GetFileSizeOnDisk(string fileName)
        {
            ulong clusterSize = 0;

            string driveLetter = System.IO.Path
                .GetPathRoot(fileName)
                .TrimEnd('\\');

            string queryString = string.Format("SELECT BlockSize, NumberOfBlocks " +
                "  FROM Win32_Volume " +
                "  WHERE DriveLetter = '{0}'", driveLetter);

            using (ManagementObjectSearcher searcher = new
                ManagementObjectSearcher(queryString))
            {
                foreach (ManagementObject item in searcher.Get())
                {
                    clusterSize = (ulong)item["BlockSize"];
                    break;
                }
            }
            uint fileSizeHigh = 0;
            uint fileSizeLow = GetCompressedFileSizeW(fileName, fileSizeHigh);
            ulong size = Convert.ToUInt64(fileSizeHigh) << 32 | fileSizeLow;
            decimal bytes = ((size + clusterSize - 1) / clusterSize) * clusterSize;

            return bytes;
        }

    }
}

Open in new window


Unit test method:

using System;
using System.Text;
using System.Collections.Generic;
using System.Linq;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using CSharp.CodeSnippets.Controls;
using CSharp.CodeSnippet.WMI;
using System.IO;

namespace Test.CodeSnippets
{
    [TestClass]
    public class WmiTests
    {

        [TestCategory("Control")]
        [TestMethod]
        public void Wmi_Win32_Volume_GetFileSizeOnDiskTest()
        {
            string fileName = Path.GetFullPath(@"..\..\..\UnitTests.xml");
            Assert.IsTrue(File.Exists(fileName), "File not found: " + fileName);
            decimal actualSize = new Win32_Volume().GetFileSizeOnDisk(fileName);

            decimal expectedSize = 28672;
            Assert.AreEqual<decimal>(expectedSize, actualSize, "Unexpected file size");
        }

    }
}

Open in new window

Commented:
I found the code you were talking about and had zero issues after testing several files of varying lengths. You may have already looked a this, but I would verify that is in fact giving you the total. The large difference makes me wonder if that is the value of a single file.

Author

Commented:
TheLearnedOne: The files are not compressed. With the example that you provided, would that work with network shares as well? In some cases, this program may point to a location like \\someserver\c$ or \\someotherserver\fileshare, so there wouldn't be a drive letter specified. There are a reasonably finite number of targets that the application will be used against, so if necessary I could just have them all mapped as network drives. I'm not really familiar with the WMI that you posted, though, so I don't know if it would work in that case.

Vaulden: Even at an individual file level, it's not returning accurate numbers. This is the code I'm using in one example:

            uint SectorsPerCluster = 0;
            uint ClusterSize = 0;
            uint NumberOfFreeClusters = 0;
            uint TotalNumberOfClusters = 0;

            GetDiskFreeSpace(@"c:\", out SectorsPerCluster, out ClusterSize, out NumberOfFreeClusters, out TotalNumberOfClusters);

            FileInfo f = new FileInfo(@"c:\temp\test.vbs");
            textBox3.Text = string.Format("{0}: Length {1:#,#} bytes, SOD {2:#,#} bytes\r\n", f.ToString(), f.Length, (ClusterSize * ((f.Length + ClusterSize - 1) / ClusterSize)));

Open in new window


The result that I get is:

c:\temp\test.vbs: Length 56 bytes, SOD 512 bytes

According to Windows Explorer, however, test.vbs has a size of 56 bytes and a size on disk of 4,096 bytes.
Become a CompTIA Certified Healthcare IT Tech

This course will help prep you to earn the CompTIA Healthcare IT Technician certification showing that you have the knowledge and skills needed to succeed in installing, managing, and troubleshooting IT systems in medical and clinical settings.

Most Valuable Expert 2012
Top Expert 2008

Commented:
If you are working with Microsoft Windows servers, then GetCompressedFileSizeW should get you what you need.  The WMI is just a mechanism that I use to access the servers remotely to get BlockSize and NumberOfBlocks for the Win32_Volume.
Anuradha GoliSystems Development / Support Specialist

Commented:
(new FileInfo(path).Length)


This example gets the size of file stored at my local c:\ drive


using System; 
using System.IO; 
namespace GET.File.Size.Example 
{ 
class Program 
{ 
static void Main() 
{ 
var fi = new FileInfo(@"c:\SQLManagementStudio_x86_ENU.exe"); 
long size = fi.Length; 
Console.WriteLine(size.ToString()); 
Console.ReadLine(); 
} 
} 
} 

Open in new window

Author

Commented:
anuradhay: Right, that gets the size of the file, but it does not return size on disk, which is the value that I'm looking for.
Commented:
I think I see the problem. You are filling ClusterSize with lpBytesPerSector from GetDiskFreeSpace. To get ClusterSize you need to multiply lpSectorsPerCluster by lpBytesPerSector.

Try:
uint SectorsPerCluster = 0;
uint BytesPerSector = 0 
uint NumberOfFreeClusters = 0;
uint TotalNumberOfClusters = 0;			
uint ClusterSize = 0;

GetDiskFreeSpace(@"c:\", out SectorsPerCluster, out BytesPerSector, out NumberOfFreeClusters, out TotalNumberOfClusters);
ClusterSize = SectorsPerCluster * BytesPerSector;
FileInfo f = new FileInfo(@"c:\temp\test.vbs");
textBox3.Text = string.Format("{0}: Length {1:#,#} bytes, SOD {2:#,#} bytes\r\n", f.ToString(), f.Length, (ClusterSize * ((f.Length + ClusterSize - 1) / ClusterSize)));

Open in new window

Do more with

Expert Office
Submit tech questions to Ask the Experts™ at any time to receive solutions, advice, and new ideas from leading industry professionals.

Start 7-Day Free Trial