|
It would be nice if the library mapped the error code to the appropriate exception. Check out Microsoft's source code for .NET to see how this is done. Specifically, the WinIOError function of the __Error.cs file. (Using .NET Reflector, do a "Search Member" for WinIOError .)
internal static void WinIOError() {
int errorCode = Marshal.GetLastWin32Error();
WinIOError(errorCode, String.Empty);
}
internal static void WinIOError(int errorCode, String maybeFullPath) {
bool isInvalidPath = errorCode == Win32Native.ERROR_INVALID_NAME || errorCode == Win32Native.ERROR_BAD_PATHNAME;
String str = GetDisplayablePath(maybeFullPath, isInvalidPath);
switch (errorCode) {
case Win32Native.ERROR_FILE_NOT_FOUND:
if (str.Length == 0)
throw new FileNotFoundException(Environment.GetResourceString("IO.FileNotFound"));
else
throw new FileNotFoundException(String.Format(CultureInfo.CurrentCulture, Environment.GetResourceString("IO.FileNotFound_FileName"), str), str);
case Win32Native.ERROR_PATH_NOT_FOUND:
if (str.Length == 0)
throw new DirectoryNotFoundException(Environment.GetResourceString("IO.PathNotFound_NoPathName"));
else
throw new DirectoryNotFoundException(String.Format(CultureInfo.CurrentCulture, Environment.GetResourceString("IO.PathNotFound_Path"), str));
case Win32Native.ERROR_ACCESS_DENIED:
if (str.Length == 0)
throw new UnauthorizedAccessException(Environment.GetResourceString("UnauthorizedAccess_IODenied_NoPathName"));
else
throw new UnauthorizedAccessException(String.Format(CultureInfo.CurrentCulture, Environment.GetResourceString("UnauthorizedAccess_IODenied_Path"), str));
case Win32Native.ERROR_ALREADY_EXISTS:
if (str.Length == 0)
goto default;
throw new IOException(Environment.GetResourceString("IO.IO_AlreadyExists_Name", str), Win32Native.MakeHRFromErrorCode(errorCode), maybeFullPath);
case Win32Native.ERROR_FILENAME_EXCED_RANGE:
throw new PathTooLongException(Environment.GetResourceString("IO.PathTooLong"));
case Win32Native.ERROR_INVALID_DRIVE:
throw new DriveNotFoundException(String.Format(CultureInfo.CurrentCulture, Environment.GetResourceString("IO.DriveNotFound_Drive"), str));
case Win32Native.ERROR_INVALID_PARAMETER:
throw new IOException(Win32Native.GetMessage(errorCode), Win32Native.MakeHRFromErrorCode(errorCode), maybeFullPath);
case Win32Native.ERROR_SHARING_VIOLATION:
if (str.Length == 0)
throw new IOException(Environment.GetResourceString("IO.IO_SharingViolation_NoFileName"), Win32Native.MakeHRFromErrorCode(errorCode), maybeFullPath);
else
throw new IOException(Environment.GetResourceString("IO.IO_SharingViolation_File", str), Win32Native.MakeHRFromErrorCode(errorCode), maybeFullPath);
case Win32Native.ERROR_FILE_EXISTS:
if (str.Length == 0)
goto default;
throw new IOException(String.Format(CultureInfo.CurrentCulture, Environment.GetResourceString("IO.IO_FileExists_Name"), str), Win32Native.MakeHRFromErrorCode(errorCode), maybeFullPath);
case Win32Native.ERROR_OPERATION_ABORTED:
throw new OperationCanceledException();
default:
throw new IOException(Win32Native.GetMessage(errorCode), Win32Native.MakeHRFromErrorCode(errorCode), maybeFullPath);
}
}
|
|
|
|
|
Thanks for the feedback. I've finally got around to submitting an update to resolve this and the other issue you reported (ASCII codes 1-31 in stream names).
"These people looked deep within my soul and assigned me a number based on the order in which I joined."
- Homer
|
|
|
|
|
ValidateStreamName incorrectly uses Path.GetInvalidFileNameChars to validate a stream name. A stream is allow characters whose integer representations are between 1 and 31. I have included a fix, as shown below.
private static char[] _invalidStreamNameChars;
public static char[] GetInvalidStreamNameChar() {
if (_invalidStreamNameChars == null) {
List<char> oResult = new List<char>();
foreach (char i in System.IO.Path.GetInvalidFileNameChars()) {
if ((i > 0) && (i < 32)) {
} else {
oResult.Add(i);
}
}
_invalidStreamNameChars = oResult.ToArray();
}
return _invalidStreamNameChars;
}
public static void ValidateStreamName(string streamName)
{
if (!string.IsNullOrEmpty(streamName) && -1 != streamName.IndexOfAny(GetInvalidStreamNameChar()))
{
throw new ArgumentException(Resources.Error_InvalidFileChars);
}
}
|
|
|
|
|
Hi,
I am having problems accessing the alternate data stream of a network shared drive. I get the error "The filename, directory name, or volume label syntax is incorrect" when I use the method FileSystem.AlternateDataStreamExists("z:\\", "xx"). Am I doing something wrong in here. Thanks in advance for your help.
--
Vinzy
|
|
|
|
|
The AlternateDataStreamExists method uses the GetFileAttributes API to determine whether a stream exists. According to the documentation[^]:
If you call GetFileAttributes for a network share, the function fails, and GetLastError returns ERROR_BAD_NETPATH . You must specify a path to a subfolder on that share.
However, I haven't been able to reproduce your error message. I've tried calling AlternateDataStreamExists with a mapped network drive and a UNC path, and both work as expected. If I pass a drive which isn't mapped, I get "The system cannot find the path specified". If I pass a UNC path without a share name, I get "The UNC path should be of the form \\server\share".
I'm running Windows 7, but I've just tried running a simplified version of the code on a Windows 2000 virtual PC and I get the same results. I've included the code below.
using System;
using System.ComponentModel;
using System.IO;
using System.Runtime.InteropServices;
static class Program
{
private const int ErrorFileNotFound = 2;
[DllImport("kernel32", CharSet = CharSet.Unicode, SetLastError = true)]
private static extern int GetFileAttributes(string fileName);
private static int SafeGetFileAttributes(string fileName)
{
if (string.IsNullOrEmpty(fileName)) throw new ArgumentNullException("fileName");
int result = GetFileAttributes(fileName);
if (-1 == result)
{
int errorCode = Marshal.GetLastWin32Error();
if (ErrorFileNotFound != errorCode) ThrowLastWin32Error();
}
return result;
}
private static void ThrowLastWin32Error()
{
int error = Marshal.GetLastWin32Error();
if (0 != error)
{
int hr = Marshal.GetHRForLastWin32Error();
if (0 > hr) Marshal.ThrowExceptionForHR(hr);
throw new Win32Exception(error);
}
}
static void Main()
{
try
{
int attributes = SafeGetFileAttributes("z:\\");
if (-1 == attributes)
{
Console.WriteLine("Drive not found");
}
else
{
Console.WriteLine("Drive found");
attributes = SafeGetFileAttributes("z:\\:xx");
if (-1 == attributes)
{
Console.WriteLine("ADS not found");
}
else
{
Console.WriteLine("ADS found");
}
}
}
catch (Exception ex)
{
Console.WriteLine(ex);
}
}
}
"These people looked deep within my soul and assigned me a number based on the order in which I joined."
- Homer
|
|
|
|
|
Hi,
Thanks for your reply. I really appreciate it.
The problem that I was having was only when I tried to access the alternate stream of the network drive.
I figured out a work around for it by finding the UNC of the network drive and then using it. It works great now. Ref: http://www.wiredprairie.us/blog/index.php/archives/22[^]
--
Vinzy
|
|
|
|
|
Strange - it works for me whether I use the UNC path or the mapped network drive.
Glad you found a workaround.
"These people looked deep within my soul and assigned me a number based on the order in which I joined."
- Homer
|
|
|
|
|
hi,
i am a newer,and i don't know how to use it.
how can i compile all the files in "Trinet.Core.IO.Ntfs" folder?
by the way i am using powershell
thanks
|
|
|
|
|
There is a pre-compiled version in the "bin" folder. Alternatively, you can build the solution using Visual Studio 2008, or from the command line using msbuild[^].
In PowerShell, make sure you're in the "Trinet.Core.IO.Ntfs" folder. To build the 32-bit version, run:
& $env:systemroot\Microsoft.NET\Framework\v3.5\msbuild.exe Trinet.Core.IO.Ntfs.sln
If you're on a 64-bit version of Windows, you can build a 64-bit version using:
& $env:systemroot\Microsoft.NET\Framework64\v3.5\msbuild.exe Trinet.Core.IO.Ntfs.sln
"These people looked deep within my soul and assigned me a number based on the order in which I joined."
- Homer
|
|
|
|
|
thanks a lot
some more
documentation in doc folder are not available
can you update?
thans again
|
|
|
|
|
The CHM file is probably blocked because you downloaded it from the Internet. To unblock it, right-click on the file and choose "Properties". At the bottom of the General tab, you will see "Security: This file came from another computer...", with an "Unblock" button next to it. Click the unblock button, and you should be able to open the file.
"These people looked deep within my soul and assigned me a number based on the order in which I joined."
- Homer
|
|
|
|
|
That's ironic, because the alternate data stream is what's causing the block on the CHM file.
Another fix is to copy the blocked file to a thumb drive (or any FAT volumne), and then back to the NTFS volume. That should effectively remove the offending alternate data streams.
".45 ACP - because shooting twice is just silly" - JSOP, 2010 ----- You can never have too much ammo - unless you're swimming, or on fire. - JSOP, 2010 ----- "Why don't you tie a kerosene-soaked rag around your ankles so the ants won't climb up and eat your candy ass." - Dale Earnhardt, 1997
|
|
|
|
|
Does FileInfo have a method called ListAlternateDataStreams()? I can't seem to find your version either.
...................
FileInfo file = new FileInfo(path);
foreach (AlternateDataStreamInfo s in file.ListAlternateDataStreams())
...................
|
|
|
|
|
It's an extension method[^] defined in the Trinet.Core.IO.Ntfs.FileSystem class. Assuming you're using the .NET 3.5 compiler, you just need to add using Trinet.Core.IO.Ntfs; to the top of your code file (or Imports Trinet.Core.IO.Ntfs for VB).
"These people looked deep within my soul and assigned me a number based on the order in which I joined."
- Homer
|
|
|
|
|
Dear friend,
i am very sorry if i am wrong. because i am new to this.
please tell me how to change the File summary information ??
i could not understand your code.
My Problem is i have own file type and i want to change the file summary through c# can u please tell me how to do that?
i searched in Google
"http://forums.asp.net/p/1050538/1483362.aspx#1483362"
from here i came to your article.
Thank you.
Joe.I
|
|
|
|
|
This is quite a complicated topic:
- For compound files, such as Office documents, the summary information is stored in the file.
- Office 2007 files can be manipulated with the OpenXML SDK[^];
- For older file versions, you need to use the DSOFile[^] component;
- In Vista, most of the summary information is stored within the file; this is why the available fields varies depending on the file type.
- In older versions of Windows, the information is stored in an alternate data stream, and can be manipulated via the IPropertySetStorage[^] interface.
Unfortunately, I've not done much work with these components, so you'd probably be better off asking in the forums.
"These people looked deep within my soul and assigned me a number based on the order in which I joined."
- Homer
|
|
|
|
|
Thanks For your Replay.
the funnything is i have posted my question in codeproject forum
and one again gave me like to your artical.
http://www.codeproject.com/Forums/1649/Csharp.aspx?fid=1649&select=3102914&fr=1#xx3102914xx
any way thank u friend for your help.
and i shall work on it.
By
Joe
|
|
|
|
|
Hi,
I need a simple sample code to writing a alternative data stream called test with this value "hello ADS" for a file and a simple code for reading the value of that data stream (test).
Is there anybody can write it for me here.
thanks
modified on Wednesday, June 10, 2009 3:24 AM
|
|
|
|
|
Try this:
using System;
using System.IO;
using Trinet.Core.IO.Ntfs;
static class Program
{
static void Main()
{
try
{
string fileName = Path.GetFullPath("Your File Path Here");
if (!File.Exists(fileName))
{
Console.WriteLine("Test file not found.");
}
else
{
WriteTestStream(fileName);
ReadTestStream(fileName);
}
}
catch (Exception ex)
{
Console.WriteLine(ex);
}
}
static void WriteTestStream(string fileName)
{
var info = FileSystem.GetAlternateDataStream(fileName, "Test");
using (var fileStream = info.OpenWrite())
using (var writer = new StreamWriter(fileStream))
{
writer.Write("hello ADS");
}
}
static void ReadTestStream(string fileName)
{
var info = FileSystem.GetAlternateDataStream(fileName, "Test");
if (info.Exists)
{
Console.WriteLine("Alternate data stream found:");
using (var reader = info.OpenText())
{
Console.WriteLine(reader.ReadToEnd());
}
}
else
{
Console.WriteLine("Alternate data stream not found: {0}", info);
}
}
}
"These people looked deep within my soul and assigned me a number based on the order in which I joined."
- Homer
|
|
|
|
|
Thank you very much.
|
|
|
|
|
On Vista SP1 it simply does not return any metadata (Count == 0), however metadata actually exists.
|
|
|
|
|
Strange - I'm using Vista SP1 (32-bit), and it works fine!
To list the streams, your process will need SE_BACKUP_NAME and SE_RESTORE_NAME privileges. To test for a specific stream, or to create, open or delete a stream, you should just need access to the file.
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using Trinet.Core.IO.Ntfs;
static class Program
{
static void Main()
{
try
{
TestStream(new FileInfo("test.jpg"));
}
catch (Exception ex)
{
Console.WriteLine(ex);
}
}
static void TestStream(FileInfo file)
{
var streams = file.ListAlternateDataStreams();
if (0 == streams.Count)
{
Console.WriteLine("No streams found.");
}
else
{
Console.WriteLine("Found {0} stream(s):", streams.Count);
foreach (var item in streams)
{
Console.WriteLine(item.Name);
}
Console.WriteLine();
}
const string ThumbnailStreamName = "thumb";
if (file.AlternateDataStreamExists(ThumbnailStreamName))
{
Console.WriteLine("Thumbnail stream exists:");
using (var stream = file.GetAlternateDataStream(ThumbnailStreamName).OpenRead())
using (var thumbnail = Image.FromStream(stream))
{
Console.WriteLine("Read thumbnail - size {0}x{1}", thumbnail.Width, thumbnail.Height);
}
file.DeleteAlternateDataStream(ThumbnailStreamName);
}
else
{
Console.WriteLine("Creating thumbnail:");
using (var inputStream = file.OpenRead())
using (var inputImage = Image.FromStream(inputStream))
{
int targetWidth = 100;
int targetHeight = 100;
int w = (int)(inputImage.Width * targetHeight / inputImage.Height);
int h = (int)(inputImage.Height * targetWidth / inputImage.Width);
if (w > targetWidth)
{
targetHeight = h;
}
else if (h > targetHeight)
{
targetWidth = w;
}
using (var thumbnail = inputImage.GetThumbnailImage(targetWidth, targetHeight, null, IntPtr.Zero))
using (var outputStream = file.GetAlternateDataStream(ThumbnailStreamName).OpenWrite())
{
thumbnail.Save(outputStream, ImageFormat.Jpeg);
Console.WriteLine("Created thumbnail: Size {0}x{1}", thumbnail.Width, thumbnail.Height);
}
}
}
}
}
Output:
...>TestStreams
No streams found.
Creating thumbnail:
Created thumbnail: Size 100x74
...>TestStreams
Found 1 stream(s):
thumb
Thumbnail stream exists:
Read thumbnail - size 100x74
"These people looked deep within my soul and assigned me a number based on the order in which I joined."
- Homer
|
|
|
|
|
Yes, the example with test.jpg works on my machine.
I apologize, perhaps I misunderstood "MetaData" term. I was looking to read MS Office 2003/2007 Summary info. This perhaps is definitely stored in file (solution is to use DSOFile.dll from Microsoft to get that data), not as NTFS data stream.
Anyway, thanks for a quick response!
|
|
|
|
|
Thank you very much for this great article!
|
|
|
|
|
According the the documentation on BackupRead(),
"lpContext [out]
Pointer to a variable that receives a pointer to an internal data structure used by BackupRead to maintain context information during a backup operation.
You must set the variable pointed to by lpContext to NULL before the first call to BackupRead for the specified file or directory. The function allocates memory for the data structure, and then sets the variable to point to that structure. You must not change lpContext or the variable that it points to between calls to BackupRead.
To release the memory used by the data structure, call BackupRead with the bAbort parameter set to TRUE when the backup operation is complete."
Also,
"bAbort [in]
Indicates whether you have finished using BackupRead on the handle. While you are backing up the file, specify this parameter as FALSE. Once you are done using BackupRead, you must call BackupRead one more time specifying TRUE for this parameter and passing the appropriate lpContext. lpContext must be passed when bAbort is TRUE; all other parameters are ignored."
Note the last statement in te first quote of the documentation: "To release the memory...". You need to call BackupRead with bAbort set to true when you are finished with the context to free the memory allocated for the context data structure.
I use alternate streams on millions of files and when I'm verifying that the alternate streams that are supposed to exist actaully do, and contain the correct information, I can use up a lot of memory.
This is a bug. I've fixed it in the code I use.
Here is snippet I added after the while(continue) { } statement to close down the BackupRead()
Kernel32.BackupRead(hFile, ref sid, dwStreamHeaderSize, ref lRead, true, false, ref Context);
I also had to move the definition of lRead but this argument is ignored.
There is also a small performance enhancement that could be achieved by moving the
"IntPtr pName = Marshal.AllocHGlobal(sid.dwStreamNameSize);" outside the while loop and moving the
"Marshal.FreeHGlobal(pName);" to the finally block. The only requirement would be to allocate enough memory for the largest alternate stream name.
scooter_jsm
|
|
|
|
|