Download Source Code: FileInformation.zip - 8.79KB
| Trivial ways to get system or genuine information about Windows files on disk. While this doesn't seem first to deserve a separate topic, there are some tricky issues on the difference between the size of a file on disk and its actual content size. Open the File Properties dialog with Win32 shell function calls. | ||
File Properties Dialog

We frequently need to programmatically access and show file information or properties, the way they appear, for instance, in File Properties dialogs you can open from virtually anywhere. File Properties window will usually appear when you have a file open or selected, and call File-Properties menu command. Most contextual menus have also a Properties command, usually at the bottom of the menu.
First tab, "General", contains most of the file properties stored and managed by the operating system itself. It may happen, for specific document and file types, you'll see other property tabs, but this information is usually stored within the file itself, the registry or somewhere else, in another repository associated to the file. This article will focus only on the genuine or system file properties.
There is no managed method that opens the File Properties window on a file. However, this can be easily done through a PInvoke call of the native ShellExecuteEx Windows function, passing the verb "properties". You'll also need to declare SHELLEXECUTEINFO as class (or struct) and the SEE_MASK enum, as well as the SW_SHOWNORMAL constant. Once this is done, here is the simple wrapper method in C#:
/// <summary>
/// Opens the File Property dialog, provided by the system
/// </summary>
/// <param name="filename">Full file path</param>
public static void ShowFileProperties(string filename)
{
SHELLEXECUTEINFO info = new SHELLEXECUTEINFO();
info.cbSize = Marshal.SizeOf(info);
info.lpVerb = "properties";
info.fMask = (int)SEE_MASK.INVOKEIDLIST;
info.nShow = (int)Win32.SW_SHOWNORMAL;
info.lpFile = filename;
Win32.ShellExecuteEx(info);
}The dialog opens as a popup within current process. So when you leave the application, all File Property dialogs will automatically close.
Genuine File Information
Common genuine file information includes the file path, file's attributes and creation/update/access times. We'll dump this property name-value pairs to either the Debug window or to the Console, calling a simple wrapper method WriteProperty.
In .NET, there are basically two immediate ways to expose information for files on disk, both using the System.IO namespace. First is through the FileInfo class, that can be simply instantiated passing the full file name:
// File Path
WriteProperty("FullName", file.FullName);
WriteProperty("Name", file.Name);
WriteProperty("Extension", file.Extension);
WriteProperty("DirectoryName", file.DirectoryName);
WriteProperty("Directory.FullName", file.Directory.FullName);
WriteProperty("ToString()", file.ToString());
// File Attributes
WriteProperty("Attributes", file.Attributes);
WriteProperty("IsReadOnly", file.IsReadOnly);
WriteProperty("Exists", file.Exists);
// File Date/Time
WriteProperty("CreationTime", file.CreationTime);
WriteProperty("CreationTimeUtc", file.CreationTimeUtc);
WriteProperty("LastAccessTime", file.LastAccessTime);
WriteProperty("LastAccessTimeUtc", file.LastAccessTimeUtc);
WriteProperty("LastWriteTime", file.LastWriteTime);
WriteProperty("LastWriteTimeUtc", file.LastWriteTimeUtc);Second is using similar static method calls from the File class:
// File Attributes
WriteProperty("GetAttributes", File.GetAttributes(filename));
WriteProperty("Exists", File.Exists(filename));
// File Date/Time
WriteProperty("GetCreationTime()",
File.GetCreationTime(filename));
WriteProperty("GetCreationTimeUtc()",
File.GetCreationTimeUtc(filename));
WriteProperty("GetLastAccessTime()",
File.GetLastAccessTime(filename));
WriteProperty("GetLastAccessTimeUtc()",
File.GetLastAccessTimeUtc(filename));
WriteProperty("GetLastWriteTime()",
File.GetLastWriteTime(filename));
WriteProperty("GetLastWriteTimeUtc()",
File.GetLastWriteTimeUtc(filename));To get and show these properties is trivial and we'll not spend time on it. Just remark that System.IO namespace offers multiple other ways to split a path into separate components (drive, directory, file name, extension). The FileAttributes value, returned by FileInfo.Attributes property or File.GetAttributes method, is a bitwise enumeration, so when you simply convert the value to a string (with ToString()), it will enumerate in clear all attribute enum names, in a comma-separated list. File time properties and methods contain alternatives for the UTC time, where the time zone of your local computer is included in the local time.
File Size
Some interesting aspects relate to the file size. This property is returned by FileInfo.Length, as the total number of bytes in the file. The same value should be found in the Size field of the File Properties dialog. To convert this value to a better display format, you can use the function below, which divides the number of bytes by powers of 1024, to show the approximated value in KB or MB:
/// <summary>
/// Properly shows a file length
/// </summary>
/// <param name="length">File length, in bytes</param>
/// <returns></returns>
public static string ToFileSize(long length)
{
// if lenght>1MB, show size in MB
const long ONE_MB = 1024L * 1024L;
if (length > ONE_MB)
return (((double)((long)(length * 100) / ONE_MB))
/ 100).ToString("#,##0.0") + " MB";
// if length>1KB, show size in KB
const long ONE_KB = 1024L;
if (length > ONE_KB)
return (((double)((long)(length * 100) / ONE_KB))
/ 100).ToString("#,##0.0") + " KB";
// show size in bytes
return length.ToString("#,###,##0") + " bytes";
}File Properties window will also show a "Size on disk" property, which is frequently different from file's Size. Why this? For two reasons:
First, the minimal storage allocation unit on disk is the cluster, which has a fixed number of bytes for the volume the file is saved on. For instance, if a 5KB file is saved on a volume with 4K cluster size, at least two clusters will be allocated for the file, so about 3K will be unused. Subsequent updates of the file can also lead to fragmentation, and the actual storage map of the file can present gaps of unused bytes, but which are allocated by the system for the file. This is why the "Size on disk" value is usually bigger than file's length.
Second, when the file is compressed, its actual size on disk can get smaller. The Size and Length properties continue to return the uncompressed file size.
To get the size on disk value, as it appears in the File Properties dialog, we need to consider both these situations, and call other two native API functions: for a compressed file - with its Compressed file attribute bit set - GetCompressedFileSize function will return its size on disk. Otherwise, we obtain the size of a cluster in current disk volume with GetDiskFreeSpace, then we apply a simple formula to see how many bytes are actually allocated for the file:
/// <summary>
/// Gets the actual length of the file on disk,
/// adding the number of bytes left in the cluster,
/// or getting the compressed file size.
/// </summary>
/// <param name="file"></param>
/// <returns></returns>
public static long GetSizeOnDisk(FileInfo file)
{
long filelength = file.Length;
if (filelength > 0)
{
// if file is compressed, gets compressed file size
if ((uint)(file.Attributes & FileAttributes.Compressed) != 0)
{
uint filesizehigh;
filelength = Win32.GetCompressedFileSize(
file.FullName, out filesizehigh);
}
// adds to the file size the number of bytes left in cluster
else
{
string rootdir = file.Directory.Root.FullName;
uint sectorsPerCluster, bytesPerSector,
freeClusters, clusters;
Win32.GetDiskFreeSpace(rootdir, out sectorsPerCluster,
out bytesPerSector, out freeClusters, out clusters);
uint clustersize = sectorsPerCluster * bytesPerSector;
filelength = (long)(clustersize
* ((filelength + clustersize - 1) / clustersize));
}
}
return filelength;
}Conclusions
While getting access to the genuine file information from .NET is in most cases simple and trivial, there are situations when we still need some Win32 function calls, with the mandatory declaration of those old structures, enum types and C function declarations.
July 06, 2007 at 02:53 PM
Michael