I have a program with a FileSystemWatcher which watches for itself to be updated to a new version by an external program (which involves renaming the current executable and copying a new one in its place).
The problem is, when the file it's watching is in the Program Files directory, the FileVersionInfo.GetVersionInfo() doesn't get the new version information, it returns the same thing it got the first time. So if it updated from 1.1 to 1.2, it would say "Upgraded from 1.1 to 1.1" instead of "Upgraded from 1.1 to 1.2". It works correctly in the debug directory, but under Program Files, it won't get the correct value.
Here's the essence of what it's doing, without all the exception handling and disposing and logging and thread invoking and such:
string oldVersion;
long oldSize;
DateTime oldLastModified;
FileSystemWatcher fs;
string fullpath;
public void Watch()
{
fullpath = Assembly.GetEntryAssembly().Location;
oldVersion = FileVersionInfo.GetVersionInfo(fullpath).ProductVersion;
var fi = new FileInfo(fullpath);
oldSize = fi.Length;
oldLastModified = fi.LastWriteTime;
fs = new FileSystemWatcher(
Path.GetDirectoryName(fullpath), Path.GetFileName(file));
fs.Changed += FileSystemEventHandler;
fs.Created += FileSystemEventHandler;
fs.EnableRaisingEvents = true;
}
void FileSystemEventHandler(object sender, FileSystemEventArgs e)
{
if (string.Equals(e.FullPath, fullpath, StringComparison.OrdinalIgnoreCase))
{
var fi = new FileInfo(fullpath);
if (fi.Length != oldSize
|| fi.LastWriteTime != oldLastModified)
{
var newversion = FileVersionInfo.GetVersionInfo(fullpath).ProductVersion;
NotifyUser(oldVersion, newversion);
}
}
}
How do I make GetVersionInfo() refresh to see the new version? Is there something else I should be calling instead?
I'm answering my own question because there doesn't seem to be much interest. If anyone has a better answer, I'll accept that instead...
As far as I can tell, there is no way to make it refresh. Instead I worked around the issue:
return AssemblyName.GetAssemblyName(fullpath).Version.ToString();
Combined with code that makes sure it only gets called once, it seems to work just fine.
Related
I have the following code:
private void Form2_Load(object sender, EventArgs e)
{
string[] files = Directory.GetFiles(Form1.programdir + "\\card_images", "*", SearchOption.TopDirectoryOnly);
MessageBox.Show(files.ElementAt(1).ToString());
PictureBox[] cards = new PictureBox[files.Count()];
for (int i = 0; i < files.Count(); i++)
{
MessageBox.Show(i.ToString());
cards[i] = new PictureBox();
cards[i].BackgroundImage = new Bitmap(Form1.programdir + "\\card_images\\" + files[i]);
MessageBox.Show(Form1.programdir + "\\card_images\\" + files[i]);
cards[i].Padding = new Padding(0);
cards[i].BackgroundImageLayout = ImageLayout.Stretch;
cards[i].MouseDown += new MouseEventHandler(card_click);
cards[i].Size = new Size((int)(this.ClientSize.Width / 2) - 15, images.Height);
images.Controls.Add(cards[i]);
}
}
Note: "images" is a FlowLayoutPanel in my program's graphic designer. I use it to easily keep track of the images.
I'm trying to get it to display images of a few poker cards, from the directory. The directory is correct, the folder is there, and previous uses of the folder have worked; Form1 is the first form of the program, and "programdir" is:
public static string programdir = Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location);
Messageboxes seem to display the correct diagnostic information (folder location, card file names, even files.Count() seems to be correct), but I can't get any files displayed in pictureboxes. Any advice?
EDIT: Apparently, the line
MessageBox.Show(Form1.programdir + "\\card_images\\" + files[i]);
Does not run, ever, at all, even though
MessageBox.Show(files.ElementAt(1).ToString());
Does run. As well,
MessageBox.Show(i.ToString());
Only runs once, outputting "0" in a messagebox (since "i" is set to 0 initially), even though it should run 4 times, since the size of the array "files" is 4.
EDIT 2: The new code, which runs flawlessly, is:
private void Form2_Load(object sender, EventArgs e)
{
Uri baseUri = new Uri(Form1.programdir + "\\card_images\\");
string[] files = Directory.GetFiles(Form1.programdir + "\\card_images", "*", SearchOption.TopDirectoryOnly);
MessageBox.Show(files.Count().ToString());
PictureBox[] cards = new PictureBox[files.Count()];
for (int i = 0; i < files.Count(); i++)
{
Bitmap bmp = null;
Uri completeUri = new Uri(baseUri, files[i]);
try
{
bmp = new Bitmap(completeUri.LocalPath);
}
catch (Exception exc)
{
// remove this if you don't want to see the exception message
MessageBox.Show(exc.Message);
continue;
}
MessageBox.Show(i.ToString());
cards[i] = new PictureBox();
cards[i].BackgroundImage = bmp;
MessageBox.Show(Form1.programdir + "\\card_images\\" + files[i]);
cards[i].Padding = new Padding(0);
cards[i].BackgroundImageLayout = ImageLayout.Stretch;
cards[i].MouseDown += new MouseEventHandler(card_click);
cards[i].Size = new Size((int)(this.ClientSize.Width / 2) - 15, images.Height);
cards[i].Visible = true;
images.Controls.Add(cards[i]);
}
}
The try/catch block isn't mandatory because it doesn't catch any errors anymore, but I'm leading it just in case something else happens some time down the road. This code will grab all images from the folder (with the assumption that only image files are in the folder, which is a separate issue), display them, and bake you a cake all at once.
So the problem appears to be that the path you are attempting to load from is invalid, because of some assumptions about the output of Directory.GetFiles(), combined with the fact that the exception you would normally expect to see is being silently dropped.
With that in mind, here's my take on a rewrite:
private void Form2_Load(object sender, EventArgs e)
{
string[] files = Directory.GetFiles(Form1.programdir + "\\card_images", "*", SearchOption.TopDirectoryOnly);
foreach (var filename in files)
{
Bitmap bmp = null;
try
{
bmp = new Bitmap(filename);
}
catch (Exception e)
{
// remove this if you don't want to see the exception message
MessageBox.Show(e.Message);
continue;
}
var card = new PictureBox();
card.BackgroundImage = bmp;
card.Padding = new Padding(0);
card.BackgroundImageLayout = ImageLayout.Stretch;
card.MouseDown += new MouseEventHandler(card_click);
card.Size = new Size((int)(this.ClientSize.Width / 2) - 15, images.Height);
images.Controls.Add(card);
}
}
I skipped creating an array since you're not keeping it anyway, and took out your diagnostic MessageBox calls. This code should load any files it finds, ignoring any that it failed to load for whatever reason, and create a PictureBox for each one that loads successfully.
Incidentally, one of the possible reasons for failure is if there are any files in the card_images folder that can't be handled by the Bitmap class. It's good, but it won't render an image from a document file or a DLL :P
Final thought: breakpoints and single-step debugging will definitely help you find the issues much faster. Visual Studio Express is sufficient for the task if you don't have anything else.
Update: Dealing with long path names
Long story short, long filenames are a pain in the rear end.
And the long story....
After a bit of checking it seems that the .NET framework by design (see this series of articles from 2007) will not handle long path names. The \\?\ prefix is rejected by all .NET classes, and UNC paths in the form //localhost/C$/........... are also rejected if they exceed MAX_PATH characters in length.
There are only a few things you can do about this:
The solution suggested by the .NET designers is to get rid of the long paths by relocating the files to a shorter path. (In other words, don't even try to use long paths.)
Create new versions of the System.IO classes with long path support. (Not for the faint of heart.)
A variety of hacks abound, including using the DefineDosDevice API (the core of the old subst dos command) to map an available drive letter to some subset of the path that fits inside the MAX_PATH limit. (Fun, but hacky.)
Use the GetShortPathName API function to turn a long path into an abbreviated version of itself using the 8.3 abbreviations created in the filesystem.
I'll focus on that last option.
public static class NativeMethods
{
const int MAX_PATH = 260;
// Force unicode version to get long-path support
[DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
static extern uint GetShortPathNameW(
[MarshalAs(UnmanagedType.LPTStr)]
string lpszLongPath,
[MarshalAs(UnmanagedType.LPTStr)]
StringBuilder lpszShortPath,
uint cchBuffer
);
public static string ShortenPath(string longPath)
{
// check if path is already short enough
if (string.IsNullOrWhiteSpace(longPath) || longPath.Length <= MAX_PATH)
return longPath;
// Get short version of path
StringBuilder shortNameBuffer = new StringBuilder(MAX_PATH);
uint result = GetShortPathNameW(#"\\?\" + longPath, shortNameBuffer, (uint)MAX_PATH);
// result is length of returned path. Must be >4 bytes to be valid
if (result <= 4)
return null;
// Get result, removing "\\?\" prefix
var res = shortNameBuffer.ToString().Substring(4);
return res;
}
}
Call NativeMethods.ShortenPath with a valid path of any length and it will try to shorten it for you if necessary. Will fail on long paths if the file couldn't be found, and might return invalid results if the resultant shortened filename is greater than MAX_PATH characters in length... which means you have 27+ directories with long names in the path, which is pretty silly anyway :P
And of course now that you've posted your workaround... it turns out that the path isn't actually too long. sigh If you ever hit the path limit, this will still help.
I am trying to delete one file which was used by certain another process of my Application.
So its giving an Error that file is used by certain another process.
private void Form1_FormClosed(object sender, FormClosedEventArgs e)
{
DirectoryInfo NewDir = new DirectoryInfo(imagefolderpath1);
FileInfo[] files = NewDir.GetFiles("*.jpg");
foreach (var item in files)
{
string strFile = imagefolderpath1 + "\\" + item.ToString();
if (File.Exists(strFile))
{
File.Delete(strFile);
}
}
}
How should i solve this problem can you please help me????
You need to kill the process which is causing this issue by the following code, something like :
string fileName = #"D:\pathname.jpg";//Path to locked file
Process Handletool = new Process();
Handletool.StartInfo.FileName = "handle.exe";
Handletool.StartInfo.Arguments = fileName+" /accepteula";
Handletool.StartInfo.UseShellExecute = false;
Handletool.StartInfo.RedirectStandardOutput = true;
Handletool.Start();
Handletool.WaitForExit();
string outputTool = Handletool.StandardOutput.ReadToEnd();
string matchPattern = #"(?<=\s+pid:\s+)\b(\d+)\b(?=\s+)";
foreach(Match match in Regex.Matches(outputTool, matchPattern))
{
Process.GetProcessById(int.Parse(match.Value)).Kill();
}
u can get Handle.exe from http://technet.microsoft.com/en-us/sysinternals/bb896655.aspx
The file needs to be released by the other program before it can be deleted. You can use Process Explorer to find out what is locking it if you don't know.
you can't access the file used by another process. But if it's not critical for you app to do this later, you can do this in the System.AppDomain.ProcessExit event handler.
just add the file to a centrally managed list and register your cleanup routine like here:
AppDomain.CurrentDomain.ProcessExit += new EventHandler(deleteFilesIfPossibleNow);
in the handler you must still handle exceptions if files are still accessed from another processes.
instead of using _FormClosed you might want to try launching the form from your other code like this:
DirectoryInfo NewDir;
FileInfo[] files;
using (var form = new Form1())
{
var result = form.ShowDialog();
if (result == DialogResult.Close)
{
NewDir = new DirectoryInfo(imagefolderpath1);
files = NewDir.GetFiles("*.jpg");
}
}
foreach(var item in files) {
string strFile = imagefolderpath1 + "\\" + item.toString();
File.Delete(strFile);
}
i wasn't a hundred % sure how your program is meant to work but you can grab information from the forms before they close then close the files they were using after with this kind of method
Does anyone know of a .Net library where a file can be copied / pasted or moved without changing any of the timestamps. The functionality I am looking for is contained in a program called robocopy.exe, but I would like this functionality without having to share that binary.
Thoughts?
public static void CopyFileExactly(string copyFromPath, string copyToPath)
{
var origin = new FileInfo(copyFromPath);
origin.CopyTo(copyToPath, true);
var destination = new FileInfo(copyToPath);
destination.CreationTime = origin.CreationTime;
destination.LastWriteTime = origin.LastWriteTime;
destination.LastAccessTime = origin.LastAccessTime;
}
When executing without administrative privileges Roy's answer will throw an exception (UnauthorizedAccessException) when attempting to overwrite existing read only files or when attempting to set the timestamps on copied read only files.
The following solution is based on Roy's answer but extends it to overwrite read only files and to change the timestamps on copied read only files while preserving the read only attribute of the file all while still executing without admin privilege.
public static void CopyFileExactly(string copyFromPath, string copyToPath)
{
if (File.Exists(copyToPath))
{
var target = new FileInfo(copyToPath);
if (target.IsReadOnly)
target.IsReadOnly = false;
}
var origin = new FileInfo(copyFromPath);
origin.CopyTo(copyToPath, true);
var destination = new FileInfo(copyToPath);
if (destination.IsReadOnly)
{
destination.IsReadOnly = false;
destination.CreationTime = origin.CreationTime;
destination.LastWriteTime = origin.LastWriteTime;
destination.LastAccessTime = origin.LastAccessTime;
destination.IsReadOnly = true;
}
else
{
destination.CreationTime = origin.CreationTime;
destination.LastWriteTime = origin.LastWriteTime;
destination.LastAccessTime = origin.LastAccessTime;
}
}
You can read and write all the timestamps there are, using the FileInfo class:
CreationTime
LastAccessTime
LastWriteTime
You should be able to read the values you need, make whatever changes you wish and then restore the previous values by using the properties of FileInfo.
Is there a way to determine with the FSW if a file or a directory has been deleted?
Here's a simplified and corrected version of fletcher's solution:
namespace Watcher
{
class Program
{
private const string Directory = #"C:\Temp";
private static FileSystemWatcher _fileWatcher;
private static FileSystemWatcher _dirWatcher;
static void Main(string[] args)
{
_fileWatcher = new FileSystemWatcher(Directory);
_fileWatcher.IncludeSubdirectories = true;
_fileWatcher.NotifyFilter = NotifyFilters.FileName;
_fileWatcher.EnableRaisingEvents = true;
_fileWatcher.Deleted += WatcherActivity;
_dirWatcher = new FileSystemWatcher(Directory);
_dirWatcher.IncludeSubdirectories = true;
_dirWatcher.NotifyFilter = NotifyFilters.DirectoryName;
_dirWatcher.EnableRaisingEvents = true;
_dirWatcher.Deleted += WatcherActivity;
Console.ReadLine();
}
static void WatcherActivity(object sender, FileSystemEventArgs e)
{
if(sender == _dirWatcher)
{
Console.WriteLine("Directory:{0}",e.FullPath);
}
else
{
Console.WriteLine("File:{0}",e.FullPath);
}
}
}
}
I temporary use the "Path" function initially, but later in case of not delete I fix it by Directory.Exists.
However that doesn't fix the Delete case
bool isDirectory = Path.GetExtension(e.FullPath) == string.Empty;
if (e.ChangeType != WatcherChangeTypes.Deleted)
{
isDirectory = Directory.Exists(e.FullPath);
}
Your question only makes sense if there could be a file and a directory with the same name at the same path. e.g. If you have filenames without extension or directories with extension.
If your directories and files follow the usual conventions, just checking for the presence of an extension in the full path(bool iSDirectory = Path.GetExtension(e.FullPath).Equals("");), which works whether the file/directory exists or not, because the method just parses the path given and has no connection to the file whatsoever.
If you have to deal with the non-conventional issues I mentioned in the beginning, you could check whether a directory or a file exists at that location. If neither does, you treat them as if both were deleted. If one of them does exist, you treat the other as if it was deleted.
Your inquiry implies that you keep a list of the files and directories somewhere, so, checking against that list, you can make your decision about handling.
I think that this approach is better than the solution given that uses two filesystem watchers in order to tell the difference.
You could interrogate the FileSystemEventArgs.FullPath property to tell if it is a directory or a file.
if (Path.GetFileName(e.FullPath) == String.Empty)
{
//it's a directory.
}
To check if it is a file or directory.
I would like to run a timer for every 5 hours and delete the files from the folder older than 4 days. Could you please with sample code?
DateTime CutOffDate = DateTime.Now.AddDays(-4)
DirectoryInfo di = new DirectoryInfo(folderPath);
FileInfo[] fi = di.GetFiles();
for (int i = 0; i < fi.Length; i++)
{
if (fi[i].LastWriteTime < CutOffDate)
{
File.Delete(fi[i].FullName);
}
}
You can substitute LastWriteTime property for something else, that's just what I use when clearing out an Image Cache in an app I have.
EDIT:
Though this doesnt include the timer part... I'll let you figure that part out yourself. A little Googling should show you several ways to do it on a schedule.
Since it hasn't been mentioned, I would recommend using a System.Threading.Timer for something like this. Here's an example implementation:
System.Threading.Timer DeleteFileTimer = null;
private void CreateStartTimer()
{
TimeSpan InitialInterval = new TimeSpan(0,0,5);
TimeSpan RegularInterval = new TimeSpan(5,0,0);
DeleteFileTimer = new System.Threading.Timer(QueryDeleteFiles, null,
InitialInterval, RegularInterval);
}
private void QueryDeleteFiles(object state)
{
//Delete Files Here... (Fires Every Five Hours).
//Warning: Don't update any UI elements from here without Invoke()ing
System.Diagnostics.Debug.WriteLine("Deleting Files...");
}
private void StopDestroyTimer()
{
DeleteFileTimer.Change(System.Threading.Timeout.Infinite,
System.Threading.Timeout.Infinite);
DeleteFileTimer.Dispose();
}
This way, you can run your file deletion code in a windows service with minimal hassle.