Created
August 19, 2023 22:23
-
-
Save exceedsystem/8cc67d6d63c3495bf47d6d52d3ea8c6b to your computer and use it in GitHub Desktop.
Sample tool for cleaning up the temporary directory under the Windows user directory in .NET 7(C#)
This file contains 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
amespace UserTempCleaner | |
{ | |
internal class Program | |
{ | |
// Threshold of elapsed time since the last use of the file/folder | |
private const int ELAPSED_HOURS = 168; | |
private static readonly string LOG_FILE = $"UserTempCleaner_{DateTime.Now:yyyyMMddHHmmss}.log"; | |
private static void Main(string[] args) | |
{ | |
WriteLogFile("Begin:"); | |
var userTempDirPath = Environment.GetEnvironmentVariable("TEMP"); | |
if (userTempDirPath == null) | |
{ | |
WriteLogFile($"Environment variable 'TEMP' not defined."); | |
return; | |
} | |
var fileTree = GetFileTree(userTempDirPath); | |
Cleanup(userTempDirPath, fileTree); | |
WriteLogFile("End:"); | |
} | |
private static FileTree GetFileTree(string path, FileTree? root = null) | |
{ | |
FileTree parent = root ?? new FileTree(null, path, true); | |
var atrb = File.GetAttributes(path); | |
if ((atrb & FileAttributes.Directory) == FileAttributes.Directory) | |
{ | |
if (Directory.Exists(path)) | |
{ | |
foreach (var dir in Directory.GetDirectories(path)) | |
{ | |
var fileTree = new FileTree(parent, dir, true); | |
var subDirInfo = GetFileTree(dir, fileTree); | |
parent.AddChild(subDirInfo); | |
} | |
} | |
} | |
foreach (var file in Directory.GetFiles(path)) | |
{ | |
var fileTree = new FileTree(parent, file); | |
parent.AddChild(fileTree); | |
} | |
return parent; | |
} | |
private static DateTime GetLastUsedTime(string path) | |
{ | |
var cTime = File.GetCreationTime(path); | |
var wTime = File.GetLastWriteTime(path); | |
var aTime = File.GetLastAccessTime(path); | |
return new[] { cTime, wTime, aTime }.Max(); | |
} | |
private static bool Cleanup(string basePath, FileTree fileTree) | |
{ | |
var files = fileTree.Children!.Where(o => !o.IsDirectory); | |
if (fileTree.Parent == null) | |
{ | |
// When the root directory is the target | |
foreach (var file in files) | |
{ | |
if (File.Exists(file.Path)) | |
{ | |
if (IsTarget(GetLastUsedTime(file.Path)) && !IsFileLocked(file.Path)) | |
{ | |
MoveToRecycleBin(basePath, file.Path, TargetType.File); | |
} | |
} | |
} | |
} | |
else | |
{ | |
// When the subdirectory is the target | |
if (!files.All(o => IsTarget(GetLastUsedTime(o.Path)) && !IsFileLocked(o.Path))) | |
{ | |
// Exclude the directory from deletion if there's even one file that cannot be deleted | |
return false; | |
} | |
} | |
var directories = fileTree.Children!.Where(o => o.IsDirectory); | |
foreach (var directory in directories) | |
{ | |
if (Cleanup(basePath, directory)) | |
{ | |
if (fileTree.Parent == null) | |
{ | |
if (Directory.Exists(directory.Path)) | |
{ | |
if (IsTarget(GetLastUsedTime(directory.Path))) | |
{ | |
MoveToRecycleBin(basePath, directory.Path, TargetType.Directory); | |
} | |
} | |
} | |
} | |
else | |
{ | |
if (fileTree.Parent != null) | |
{ | |
return false; | |
} | |
} | |
} | |
return true; | |
} | |
private static bool IsTarget(DateTime dtm) | |
{ | |
var diff = DateTime.Now.Subtract(dtm); | |
return (diff.TotalHours >= ELAPSED_HOURS); | |
} | |
private static void MoveToRecycleBin(string basePath, string path, TargetType targetType) | |
{ | |
if (!path.StartsWith(basePath, StringComparison.OrdinalIgnoreCase)) | |
{ | |
// Path mismatch (potential for directory traversal) | |
WriteLogFile($"Ignored({targetType.ToString().ToLower()}):'{path}'"); | |
return; | |
} | |
try | |
{ | |
switch (targetType) | |
{ | |
case TargetType.Directory: | |
Microsoft.VisualBasic.FileIO.FileSystem.DeleteDirectory( | |
path, | |
Microsoft.VisualBasic.FileIO.UIOption.OnlyErrorDialogs, | |
Microsoft.VisualBasic.FileIO.RecycleOption.SendToRecycleBin); | |
break; | |
case TargetType.File: | |
Microsoft.VisualBasic.FileIO.FileSystem.DeleteFile( | |
path, | |
Microsoft.VisualBasic.FileIO.UIOption.OnlyErrorDialogs, | |
Microsoft.VisualBasic.FileIO.RecycleOption.SendToRecycleBin); | |
break; | |
} | |
WriteLogFile($"Deleted({targetType.ToString().ToLower()}):'{path}'"); | |
} | |
catch (Exception e) | |
{ | |
WriteLogFile($"Error:Could not delete {targetType.ToString().ToLower()} '{path}'. ({e.Message})"); | |
} | |
} | |
private static bool IsFileLocked(string filePath) | |
{ | |
try | |
{ | |
new FileStream(filePath, FileMode.Open, FileAccess.ReadWrite, FileShare.None).Dispose(); | |
} | |
catch (IOException) | |
{ | |
return true; | |
} | |
return false; | |
} | |
private static void WriteLogFile(string msg) | |
{ | |
var logMsg = $"{DateTime.Now:yyyyMMddHHmmss}:{msg}"; | |
Console.WriteLine(logMsg); | |
File.AppendAllLines(LOG_FILE, new[] { logMsg }); | |
} | |
private sealed class FileTree | |
{ | |
public List<FileTree>? _children; | |
public FileTree(FileTree? parent, string path, bool isDirectory = false) | |
{ | |
Parent = parent; | |
Path = path; | |
if (isDirectory) | |
{ | |
_children = new List<FileTree>(); | |
} | |
} | |
public FileTree? Parent { get; init; } | |
public string Path { get; init; } | |
public bool IsDirectory => _children != null; | |
public IReadOnlyList<FileTree>? Children => _children; | |
public void AddChild(FileTree fileTree) => _children?.Add(fileTree); | |
public override string ToString() => $"Path={Path}, Children={Children?.Count}"; | |
} | |
private enum TargetType | |
{ | |
Directory, | |
File | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment