Skip to content

Instantly share code, notes, and snippets.

@mjs3339
Created June 3, 2019 21:04
Show Gist options
  • Save mjs3339/74e261bea40d5f3207d82c6972f6e341 to your computer and use it in GitHub Desktop.
Save mjs3339/74e261bea40d5f3207d82c6972f6e341 to your computer and use it in GitHub Desktop.
C# File Data Class
using System;
using System.Diagnostics.Contracts;
using System.IO;
using System.Management;
using System.Runtime.InteropServices;
using System.Security;
using Microsoft.Win32.SafeHandles;
[SecurityCritical]
[Serializable]
public class FileData
{
public static readonly char DirectorySeparatorChar = '\\';
public static readonly char AltDirectorySeparatorChar = '/';
public static readonly char VolumeSeparatorChar = ':';
private int _bytesPerSector;
private long _clusterSize;
/// <summary>
/// A copy of the raw finddata structure
/// </summary>
public WIN32_FIND_DATA Data;
/// <summary>
/// Full path name and extension of the file.
/// </summary>
public string FullName;
/// <summary>
/// Create a FileData structure using a path and a win32_find_data class.
/// </summary>
[SecuritySafeCritical]
public FileData(string path, WIN32_FIND_DATA findData)
{
FullName = path + @"\" + findData.cFileName;
Data = CopyTo(findData);
}
[SecuritySafeCritical]
public FileData(string filePath)
{
FullName = filePath;
var data = default(WIN32_FILE_ATTRIBUTE_DATA);
if (FillAttributeInfo(filePath, ref data) == -1)
return;
Data = data.PopulateTo();
}
/// <summary>
/// Clusters size of the underlying file system.
/// </summary>
public long ClusterSize
{
get
{
if (_clusterSize == 0)
{
_clusterSize = GetClusterSize(FullName.Substring(0, 3).TrimEnd('\\'));
return _clusterSize;
}
return _clusterSize;
}
}
/// <summary>
/// Byte Per Sector of the underlying disk structure.
/// </summary>
public int BytesPerCluster
{
get
{
if (_bytesPerSector == 0)
{
_bytesPerSector = GetBytesPerSector(FullName.Substring(0, 1));
return _bytesPerSector;
}
return _bytesPerSector;
}
}
/// <summary>
/// Attributes of the file.
/// </summary>
public FileAttributes Attributes => Data.dwFileAttributes;
/// <summary>
/// File creation time in UTC
/// </summary>
public DateTime CreationTimeUtc => ConvertDateTime((uint) Data.ftCreationTime.dwHighDateTime,
(uint) Data.ftCreationTime.dwLowDateTime);
/// <summary>
/// Just the path root:/folder
/// </summary>
public string DirectoryName => GetDirectoryName(FullName);
/// <summary>
/// Extension of the file type only DOES include the .DOT
/// </summary>
public string Extension => GetExtension(Name);
/// <summary>
/// The file clusters information. (SEE: FileFragments.cs)
/// </summary>
public FileFragments.FileFragment FileClusters => FileFragments.GetFileAllocation(FullName);
/// <summary>
/// FileName Only does NOT include the .DOT or extension
/// </summary>
public string FileName => GetFileNameWithoutExtension(Name);
/// <summary>
/// File last access time in UTC
/// </summary>
public DateTime LastAccessTimeUtc => ConvertDateTime((uint) Data.ftLastAccessTime.dwHighDateTime,
(uint) Data.ftLastAccessTime.dwLowDateTime);
/// <summary>
/// File last write time in UTC
/// </summary>
public DateTime LastWriteTimeUtc => ConvertDateTime((uint) Data.ftLastWriteTime.dwHighDateTime,
(uint) Data.ftLastWriteTime.dwLowDateTime);
/// <summary>
/// Size of the file in bytes
/// </summary>
public long Length => CombineHighLowInts(Data.nFileSizeHigh, Data.nFileSizeLow);
public long SlackSpace => GetFileSlackSpace();
/// <summary>
/// Just the name of the file with extension
/// </summary>
public string Name => GetFileName(FullName);
/// <summary>
/// Size of the file in bytes on the disk
/// </summary>
public long SizeOnDisk => GetFileSizeOnDisk(FullName);
public DateTime CreationTime => CreationTimeUtc.ToLocalTime();
public DateTime LastAccesTime => LastAccessTimeUtc.ToLocalTime();
public DateTime LastWriteTime => LastWriteTimeUtc.ToLocalTime();
[SecurityCritical]
private static int FillAttributeInfo(string path, ref WIN32_FILE_ATTRIBUTE_DATA data)
{
bool success;
var oldMode = Win32.SetErrorMode(1);
try
{
success = Win32.GetFileAttributesEx(path, 0, ref data);
}
finally
{
Win32.SetErrorMode(oldMode);
}
if (!success)
{
var errormode = Marshal.GetLastWin32Error();
if (errormode != 0x2 && errormode != 0x3 && errormode != 0x15)
{
var tempPath = path.TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar);
oldMode = Win32.SetErrorMode(1);
try
{
var handle = FindFirstFile(tempPath, out var findData);
try
{
if (handle.IsInvalid)
return -1;
}
finally
{
try
{
handle.Close();
}
catch
{
}
}
data.PopulateFrom(findData);
}
finally
{
Win32.SetErrorMode(oldMode);
}
}
else
{
return -1;
}
}
return 0;
}
[SecuritySafeCritical]
public static long CombineHighLowInts(uint high, uint low)
{
if (high == 0)
return low;
return ((long) high << 0x20) | low;
}
[SecuritySafeCritical]
private static DateTime ConvertDateTime(uint high, uint low)
{
return DateTime.FromFileTimeUtc(CombineHighLowInts(high, low));
}
[SecuritySafeCritical]
public static bool FileExists(string filePath)
{
WIN32_FIND_DATA data;
using (var findHandle = FindFirstFile(filePath, out data))
{
return !findHandle.IsInvalid;
}
}
public static long GetFileSize(string path)
{
if (path.Length + 1 > 260)
return -1;
WIN32_FIND_DATA Datax;
using (var findHandle = FindFirstFile(path, out Datax))
{
if (findHandle.IsInvalid)
return -1;
return ((long) Datax.nFileSizeHigh << 0x20) | Datax.nFileSizeLow;
}
}
[SecuritySafeCritical]
[Pure]
private static string GetExtension(string path)
{
if (path != null)
{
int i;
if ((i = path.LastIndexOf('.')) == -1)
return string.Empty;
return path.Substring(i);
}
return string.Empty;
}
[SecuritySafeCritical]
[Pure]
public static string GetFileName(string path)
{
if (path != null)
{
var length = path.Length;
for (var i = length; --i >= 0;)
{
var ch = path[i];
if (ch == DirectorySeparatorChar || ch == AltDirectorySeparatorChar || ch == VolumeSeparatorChar)
return path.Substring(i + 1, length - i - 1);
}
}
return path;
}
[SecuritySafeCritical]
[Pure]
public static string GetFileNameWithoutExtension(string path)
{
if (path != null)
{
int i;
if ((i = path.LastIndexOf('.')) == -1)
return path;
return path.Substring(0, i);
}
return null;
}
[SecuritySafeCritical]
[Pure]
public static string GetDirectoryName(string path)
{
if (path == null)
return null;
var pos = path.LastIndexOf(DirectorySeparatorChar);
if (pos == -1 || pos + 1 > path.Length)
return null;
return path.Substring(0, pos + 1);
}
/// <summary>
/// Returns the first rooted directory in the path. i.e. C:\Windows\ from C:\Windows\System32
/// </summary>
[SecuritySafeCritical]
[Pure]
public static string GetRootDirectoryName(string path)
{
if (path == null)
return null;
var fullpath = GetDirectoryName(path);
var pos = fullpath.IndexOf(DirectorySeparatorChar);
if (pos == -1 || pos + 1 > path.Length)
return null;
return fullpath.Substring(0, pos + 1);
}
/// <summary>
/// Combines path1 and path2
/// </summary>
[SecuritySafeCritical]
[Pure]
public static string Combine(string path1, string path2)
{
if (path2 == null && path1 == null)
throw new Exception("Error: FileData|Combine, both path1 and path2 cannot be null.");
if (path2 == null)
return path1;
if (path1 == null)
return path2;
if (path2.Length == 0)
return path1;
if (path1.Length == 0)
return path2;
if (IsPathRooted(path2))
return path2;
var ch = path1[path1.Length - 1];
if (ch != DirectorySeparatorChar && ch != AltDirectorySeparatorChar && ch != VolumeSeparatorChar)
return path1 + DirectorySeparatorChar + path2;
return path1 + path2;
}
[SecuritySafeCritical]
[Pure]
private static bool IsPathRooted(string path)
{
if (path == null)
return false;
var length = path.Length;
return length >= 1 && (path[0] == DirectorySeparatorChar || path[0] == AltDirectorySeparatorChar) ||
length >= 2 && path[1] == VolumeSeparatorChar;
}
[DllImport("kernel32", CharSet = CharSet.Auto, SetLastError = true)]
private static extern SafeFindHandle FindFirstFile(string lpFileName, out WIN32_FIND_DATA lpFindFileData);
[DllImport("kernel32.dll")]
private static extern uint GetCompressedFileSizeW([In] [MarshalAs(UnmanagedType.LPWStr)] string lpFileName,
[Out] [MarshalAs( UnmanagedType.U4)] uint lpFileSizeHigh);
[DllImport("kernel32.dll", SetLastError = true, EntryPoint = "GetCompressedFileSize")]
private static extern uint GetCompressedFileSizeAPI(string lpFileName, out uint lpFileSizeHigh);
[SecuritySafeCritical]
public static long GetClusterSize(string drive)
{
long clusterSize = 0;
var driveLetter = drive.TrimEnd('\\');
var queryString = "SELECT BlockSize, NumberOfBlocks " + " FROM Win32_Volume " +
$" WHERE DriveLetter = '{driveLetter}'";
using (var searcher = new ManagementObjectSearcher(queryString))
{
foreach (ManagementObject item in searcher.Get())
{
clusterSize = item["BlockSize"].ToString().ToInt64();
break;
}
}
return clusterSize;
}
[SecuritySafeCritical]
public static int GetBytesPerSector(string drive)
{
var sto = new Storage();
var sectorSize = 0;
var disk = sto.GetDiskFromDrive(drive.Substring(0, 1)).ToInt32();
var queryString = "SELECT * FROM Win32_DiskDrive";
using (var searcher = new ManagementObjectSearcher(queryString))
{
foreach (ManagementObject item in searcher.Get())
if (item["DeviceID"].ToString()
.IndexOf($"\\\\.\\PHYSICALDRIVE{disk}", StringComparison.OrdinalIgnoreCase) != -1)
{
sectorSize = item["BytesPerSector"].ToString().ToInt32();
break;
}
}
return sectorSize;
}
[SecuritySafeCritical]
public long GetFileSizeOnDisk(string FileName)
{
var LowOrder = GetCompressedFileSizeAPI(FileName, out var HighOrder);
var error = Marshal.GetLastWin32Error();
long size = 0;
if (HighOrder == 0 && LowOrder == 0xFFFFFFFF && error != 0)
return Length;
size = (Convert.ToInt64(HighOrder) << 32) | LowOrder;
var tests = (size + ClusterSize - 1) / ClusterSize * ClusterSize;
var ts = ToTheNearestCluster(size);
return ts;
}
private long ToTheNearestCluster(long size)
{
var clusterUsed = size / ClusterSize;
if (clusterUsed < size * ClusterSize)
clusterUsed++;
return clusterUsed * ClusterSize;
}
[SecuritySafeCritical]
public long GetFileSlackSpace()
{
return ToTheNearestCluster(Length) - Length;
}
[SecuritySafeCritical]
public override string ToString()
{
return FullName;
}
[SecurityCritical]
private static WIN32_FIND_DATA CopyTo(WIN32_FIND_DATA sfindData)
{
var tfindData = new WIN32_FIND_DATA
{
dwFileAttributes = sfindData.dwFileAttributes,
ftCreationTime =
{
dwLowDateTime = sfindData.ftCreationTime.dwLowDateTime,
dwHighDateTime = sfindData.ftCreationTime.dwHighDateTime
},
ftLastAccessTime =
{
dwLowDateTime = sfindData.ftLastAccessTime.dwLowDateTime,
dwHighDateTime = sfindData.ftLastAccessTime.dwHighDateTime
},
ftLastWriteTime =
{
dwLowDateTime = sfindData.ftLastWriteTime.dwLowDateTime,
dwHighDateTime = sfindData.ftLastWriteTime.dwHighDateTime
},
nFileSizeHigh = sfindData.nFileSizeHigh,
nFileSizeLow = sfindData.nFileSizeLow,
cAlternate = sfindData.cAlternate,
cFileName = sfindData.cFileName
};
return tfindData;
}
[SecurityCritical]
internal class SafeFindHandle : SafeHandleZeroOrMinusOneIsInvalid
{
[SecurityCritical]
internal SafeFindHandle()
: base(true)
{
}
[DllImport("kernel32.dll", SetLastError = true)]
private static extern bool FindClose(IntPtr hFindFile);
[SecurityCritical]
protected override bool ReleaseHandle()
{
return FindClose(handle);
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment