Tuesday, March 25, 2008

Remove Files Read-only Attribute In Windows

A Unix machine that's not in our control is running a daily process of copying files in folders to a Windows server under our control by rsync. But some of the files have read-only permission in the target Windows machine, and these read-only files in Windows cause some problem in a separate file-handling process.

To remove a file read-only permission in Windows we can use attrib command:
attrib -r filename
About DOS command will remove all attributes from the file. But attrib can only handle single file. In Unix/Linux we can simply call "chomd -R 777 folder" to recursively change the permission of all files in the folder, but not that easy in Windows.

To do this unix-simple-task in Windows, I created a console application:
using System;
using System.Collections.Generic;
using System.Text;
using System.Configuration;
using System.IO;
using System.Security.AccessControl;

namespace Bell.Sympatico.Utilities.ReadOnlyRemover
{
/// <summary>
/// This console application is to remove file's read only attribute
/// The user who runs the application needs the modify permission for the files
/// Application configuration file name: ReadOnlyRemover.exe.config
///
/// Usage:
/// 1. Process all files in a folder and its subfolders.
/// Specify the folder (RootFolder entry) in App.config configuration file
/// C:\ApplicationFolder>ReadOnlyRemover.exe /all
/// 2. Process feed and/or log files.
/// Specify the file name for Feed files (FeedFile entry in configuration file) and/or
/// Specify the file name for Log files (LogFile entry in configuration file)
/// C:\ApplicationFolder>ReadOnlyRemover.exe
/// 3. Process all files in a folder and its subfolders. But the folder name is
/// provided in the arguments. The folder name can be a share folder in network.
/// You need the assign read/write permission for the network shared folder in
/// such case.
/// C:\ApplicationFolder>ReadOnlyRemover.exe /all c:\test
/// C:\ApplicationFolder>ReadOnlyRemover.exe /all \\NetworkShared\Feed1
///
/// A application log file will be created or appended during the process.
/// You can define the application log file name in configuration file (AppLogFile entry)
/// </summary>
class ReadOnlyRemover
{
static StreamWriter sw = null; // Write the logging in a file.
static bool doLog = true; // Do we need to do the logging during the processing?

/// <summary>
/// The entry point of the application
/// </summary>
/// <param name="args">Arguments</param>
static void Main(string[] args)
{
bool scanAll = false; // A flag if check all files inside a folder
if (args.Length > 0 && (args[0].ToLower() == "/all" || args[0].ToLower() == "all"))
{
scanAll = true;
}

string rootFolder = ConfigurationManager.AppSettings["RootFolder"];
if (string.IsNullOrEmpty(rootFolder) || !Directory.Exists(rootFolder))
{
rootFolder = "C:/Feed/"; // Default folder name
}
else if (!rootFolder.EndsWith("/") && !rootFolder.EndsWith("\\"))
{
rootFolder = rootFolder + "/";
}

if (scanAll && args.Length > 1)
{
string argument = args[1];
if (Directory.Exists(argument))
{
rootFolder = argument;
}
else
{
Console.WriteLine("Specified folder invalid!");
return;
}
}

string appLogFile = ConfigurationManager.AppSettings["AppLogFile"];
if (string.IsNullOrEmpty(appLogFile))
{
appLogFile = "ReadOnlyRemover.log.txt"; // Default application log name
}

try
{
sw = new StreamWriter(appLogFile, true);
sw.WriteLine("Process starts at " + DateTime.Now.ToString("yyyy/MM/dd HH:mm"));
sw.Flush();
}
catch (Exception ex)
{
doLog = false; // Do not write the log in the file in such case
Console.WriteLine("Could not create or open application log file. Error: " + ex.Message);
Console.WriteLine("Continue to process without logging.");
}

if (scanAll)
{
UpdateFolderRecursive(rootFolder);
}
else // Process the files that are listed in feedFile and/or logFile
{
bool hasFeedFile = true;
string feedFile = ConfigurationManager.AppSettings["FeedFile"];
if (string.IsNullOrEmpty(feedFile) || !File.Exists(feedFile))
{
Console.WriteLine("Miss Feed file!");
sw.WriteLine("Miss Feed file!");
hasFeedFile = false;
}
List<string> feedNameList = null;
if (hasFeedFile)
{
feedNameList = GetFileNames(feedFile, rootFolder);
UpdateFilesAttribute(feedNameList);
}

bool hasLogFile = true;
string logFile = ConfigurationManager.AppSettings["LogFile"];
if (string.IsNullOrEmpty(logFile) || !File.Exists(logFile))
{
Console.WriteLine("Miss Log file!");
sw.WriteLine("Miss LOg file!");
hasLogFile = false;
}
List<string> logNameList = null;
if (hasLogFile)
{
logNameList = GetFileNames(logFile, rootFolder);
UpdateFilesAttribute(logNameList);
}
}
if (doLog && sw != null)
{
sw.Close();
}
}

/// <summary>
/// Read a file storing all file names that need to be processed
/// Put all file names into a list
/// </summary>
/// <param name="fileName">File Name</param>
/// <param name="rootFolder">The root folder that holds the file</param>
private static List<string> GetFileNames(string fileName, string rootFolder)
{
List<string> fileNameList = new List<string>();
if (File.Exists(fileName))
{
try
{
using (StreamReader sr = new StreamReader(fileName))
{
while (sr.Peek() >= 0)
{
fileNameList.Add(rootFolder + sr.ReadLine());
}
}
}
catch (Exception ex)
{
string logString = DateTime.Now.ToString("yyyy/MM/dd HH:mm") + " - Error in reading file " + fileName + ": " + ex.Message;
if (doLog)
{
sw.WriteLine(logString);
sw.Flush();
}
else
{
Console.WriteLine(logString);
}
}
}
return fileNameList;

}

/// <summary>
/// Go throught all files and remove the read only attribute
/// </summary>
/// <param name="fileNames">File names</param>
private static void UpdateFilesAttribute(List<string> fileNames)
{
foreach (string file in fileNames)
{
if (File.Exists(file))
{
RemoveFileReadonlyAttribute(file);
}
else
{
/*
string logString = DateTime.Now.ToString("yyyy/MM/dd HH:mm") + " - Not a valid file: " + file;
if (doLog)
{
sw.WriteLine(logString);
sw.Flush();
}
else
{
Console.WriteLine(logString);
}
*/

}
}
}

/// <summary>
/// Recursively check all files in the folder and its subfolders
/// Remove read only for all files
/// </summary>
/// <param name="path">Folder Name</param>
private static void UpdateFolderRecursive(string path)
{
if (Directory.Exists(path))
{
string[] files = Directory.GetFileSystemEntries(path);
foreach (string entry in files)
{
if (Directory.Exists(entry))
{
// It's a folder, dig into it's subfolders
UpdateFolderRecursive(entry);
}
else if (File.Exists(entry))
{
// It's a file, check the attribute
RemoveFileReadonlyAttribute(entry);
}
}
}
}

/// <summary>
/// Check if the file is read only. if so remove the read only attribute.
/// </summary>
/// <param name="fileName">file name</param>
private static void RemoveFileReadonlyAttribute(string fileName)
{
if (!File.Exists(fileName))
return;

FileInfo fi = new FileInfo(fileName);
if ((fi.Attributes & FileAttributes.ReadOnly) == FileAttributes.ReadOnly)
{
try
{
// Remove the read only attribute
fi.Attributes -= FileAttributes.ReadOnly;
StringBuilder sb = new StringBuilder();
sb.Append(DateTime.Now.ToString("yyyy/MM/dd HH:mm"));
sb.Append(" - Read only attribute of file ");
sb.Append(fileName.Replace("\\", "/"));
sb.Append(" has been removed.");
if (doLog)
{
sw.WriteLine(sb.ToString());
sw.Flush();
}
else
{
Console.WriteLine(sb.ToString());
}
}
catch (Exception ex)
{
string logString = DateTime.Now.ToString("yyyy/MM/dd HH:mm") + " - Error in updating " + fileName + " attribute: " + ex.Message;
if (doLog)
{
sw.WriteLine(logString);
sw.Flush();
}
else
{
Console.WriteLine(logString);
}
}
}
}

/// <summary>
/// Add File Security for a file
/// AddFileSecurity(@"c:\Feed\abc.txt", "Everyone", FileSystemRights.FullControl, AccessControlType.Allow);
/// </summary>
/// <param name="filep">File Path</param>
/// <param name="account">Acount for the permission</param>
/// <param name="right">User right</param>
/// <param name="controlType">Allow or Deny</param>
public static void AddFileSecurity(string file, string account, FileSystemRights right, AccessControlType controlType)
{
FileSecurity fileSec = File.GetAccessControl(file);
fileSec.AddAccessRule(new FileSystemAccessRule(account, right, controlType));
File.SetAccessControl(file, fileSec);
}
}
}