Created
June 3, 2019 21:04
-
-
Save mjs3339/74e261bea40d5f3207d82c6972f6e341 to your computer and use it in GitHub Desktop.
C# File Data Class
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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