How to use SQLInstallDriverEx in C#? - c#

How would I rewrite the following function in order to use it in C#? The function is part of odbccp32.dll and is used to install an ODBC driver.
BOOL SQLInstallDriverEx(
LPCSTR lpszDriver,
LPCSTR lpszPathIn,
LPSTR lpszPathOut,
WORD cbPathOutMax,
WORD * pcbPathOut,
WORD fRequest,
LPDWORD lpdwUsageCount);
SQLInstallDriverEx Reference
This is my attempt:
[DllImport("odbccp32")]
private static extern bool SQLInstallDriverEx(
string lpszDriver,
string lpszPathIn,
out string lpszPathOut,
ushort cbPathOutMax,
out uint pcbPathOut,
ushort fRequest,
out uint lpdwUsageCount);
A sample usage would be deeply appreciated as well.

p/Invoke Toolkit generates the following:
[DllImport("odbccp32.dll")]
public static extern bool SQLInstallDriverEx(
[In, MarshalAs(UnmanagedType.LPStr)] string lpszDriver,
[In, MarshalAs(UnmanagedType.LPStr)] string lpszPathIn,
[MarshalAs(UnmanagedType.LPStr)] System.Text.StringBuilder lpszPathOut,
ushort cbPathOutMax, ref ushort pcbPathOut, ushort fRequest, ref uint lpdwUsageCount);
public const ushort ODBC_INSTALL_INQUIRY = 1;
public const ushort ODBC_INSTALL_COMPLETE = 2;
The In/MarshalAs attributes may not be necessary. StringBuilders work for "out" strings as the marshaller knows how to convert under the hood. You need to call the method twice, once to resolve the path/path length and the second to pass those values in. That step can be skipped if you know these before hand, but the path can change from what you provide if the driver already exists or the path is too long. The second call requires that the lpszPathOut be non-null and cbPathOutMax be non-zero.
The driver description that you pass to the method is made up of a list of null-byte terminated strings. The list is terminated with a double null.
// if target path is null, it should default to the system directory
var targetPath = #"c:\windows\system32";
ushort len = 0; uint usageCount=0;
// replace this with your actual driver description, note the double \0 at the end
var driver = "Text\0Driver=TEXT.DLL\0Setup=TXTSETUP.DLL\0FileUsage=1\0"
+ "FileExtns=*.txt,*.csv\0\0";
var ret = SQLInstallDriverEx(driver, targetPath, null, 0,
ref len, ODBC_INSTALL_INQUIRY, ref usageCount);
if (ret) {
var sbPath = new StringBuilder(len);
if (ret = SQLInstallDriverEx(driver, targetPath, sbPath, len,
ref len, ODBC_INSTALL_COMPLETE, ref usageCount)) {
// sbPath will now contain the actual path, which may be different
// if there is a previous installation or the path was truncated
// perhaps check here if that was intended
// usageCount will be updated by the system
}
}
if (!ret) {
// query SqlInstallerError(...) here
}
If the return value is false, you will need to call the SqlInstallerError method which you will have to import as well. Fortunately there is a p/invoke page for this method and how to call it available at http://www.pinvoke.net/default.aspx/odbccp32/SQLInstallerError.html. The error codes and what they mean are already outlined on the MSDN page you linked.
Note that you can skip the two-phase method call and call with ODBC_INSTALL_COMPLETE once, provided you have provided correct buffer/sizes.

Related

Calling unmanaged c++ code from managed C# code to generate offline domain join blob

I am writing a piece of program that generates Offline Domain Join blob and saves it for future use. This action can be done using command prompt. Below is a sample command that will generate the mentioned file and save it on D drive:
D:\djoin.exe /REUSE /PROVISION /DOMAIN MyDomain.MyCompany.com /MACHINE "user1-pc" /SAVEFILE blob.txt
More information: Offline Domain Join (Djoin.exe) Step-by-Step Guide
Now, I want to add a method to my program (written with C#) to does this functionality for me.
One of the problems here is, the API that Microsoft has provided is a C++ API. I have tried to use the API in managed code using PInvoke. Below is the code I have written.
using System;
using System.Runtime.InteropServices;
namespace TestBlob
{
public class Program
{
static void Main(string[] args)
{
String domain = "MyDomain.MyCompany.com";
String machineName = "user1-pc";
String machineAccoutOU = null;
String dcName = "MyDomain";
uint options = 1;
IntPtr provisionBinData = IntPtr.Zero;
IntPtr provisionBinDataSize = IntPtr.Zero;
string blob = string.Empty;
IntPtr pProvisionTextData = Marshal.StringToHGlobalUni(blob);
uint status = ODJNativeMethods.NetProvisionComputerAccount(domain, machineName, machineAccoutOU, dcName, options, ref provisionBinData, ref provisionBinDataSize, ref pProvisionTextData);
Console.WriteLine(status);
Console.WriteLine(Marshal.PtrToStringUni(pProvisionTextData));
Console.Read();
}
}
public static class NativeMethods
{
[DllImport("Netapi32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
public static extern uint NetProvisionComputerAccount([In] String lpDomain,
[In] String lpMachineName,
[In] String lpMachineAccountOU,
[In] String lpDcName,
[In] uint dwOptions,
[In] [Out] ref IntPtr pProvisionBinData,
[In] [Out] ref IntPtr pdwProvisionBinDataSize,
[In] [Out] ref IntPtr pProvisionTextData);
}
}
When I run the application, it always returns 87 (shows on console), which after a quick search turns out to be an error message: The parameter is invalid.
What am I doing wrong here? Are my PInvoke types not the correct ones corresponding to native language API?
The 3 last parameters are declared out, which means you must not initialize them, but pass correct pointer so the function can allocate things for you.
Also, from what I understand reading the function doc, the binary one and the string one are mutually exclusive, so let's say you want to get back the binary one, then you can define the API like this ([in] are usually implicit):
[DllImport("netapi32.dll", CharSet = CharSet.Unicode)]
public static extern int NetProvisionComputerAccount(
string lpDomain,
string lpMachineName,
string lpMachineAccountOU,
string lpDcName,
int dwOptions,
out IntPtr pProvisionBinData,
out int pdwProvisionBinDataSize,
IntPtr pProvisionTextData);
Note the function does not use SetLastError, so don't declare it in the declaration.
And here is how to call it:
string domain = "MyDomain.MyCompany.com";
string machineName = "user1-pc";
string machineAccoutOU = null;
string dcName = "MyDomain";
// I suggest you don't use hardcoded values to be nice with readers
const int NETSETUP_PROVISION_DOWNLEVEL_PRIV_SUPPORT = 1;
int status = NetProvisionComputerAccount(
domain,
machineName,
machineAccoutOU,
dcName,
NETSETUP_PROVISION_DOWNLEVEL_PRIV_SUPPORT,
out IntPtr binData, // let the system allocate the binary thing
out int binDataSize, // this will be the size of the binary thing
IntPtr.Zero); // we don't use this one here, pass null
I can't test further (I get error 1354 which I suppose is normal in my context).
Note the doc doesn't say anything about deallocating what the function allocates (if it allocates something? there are some rare Windows API that use static buffers they own). I think you're supposed to call NetApiBufferFree on binData once all work is done, but it's just a guess.
It could be the encoding of your text file - if you have saved it as ANSI and consuming it as Unicode, it might not work.
Use these statements corresponding to your above code:
IntPtr pProvisionTextData = Marshal.StringToHGlobalAnsi(blob);
and
[DllImport("user32", CharSet=CharSet::Ansi)]
Hope it helps.

SCardGetCardTypeProviderName return empty results

I'm trying to use the SCardGetCardTypeProviderName using interop in C#. One of the parameters is a reference, that is supposed to return the name of the provider for a smart card according to the card context that is passed in. This is the code I'm using:
IntPtr hSC = { value comes from call using SCardEstablishContext }
string cardName = { value comes from SCardUIDlgSelectCard }
int providerNameLength = 256;
string providerName = string.Empty; //doesn't matter how I initialize this
[DllImport("winscard.dll", CharSet = CharSet.Ansi, SetLastError = true)]
public static extern Int32 SCardGetCardTypeProviderName(IntPtr hContext, string szCardName, uint dwProviderId, ref string szProvider, ref int pcchProvider);
int lReturn = SCardGetCardTypeProviderName(hSC, cardName, SCARD_PROVIDER_CSP, ref providerName, ref providerNameLength);
But the providerName always comes back empty, while the providerNameLength changes to 43, which makes me think it's a marshalling problem. But I've tried marshalling providerName as:
LPWStr
LPTStr
LPStr
In the case of the latter two, instead of an empty string, I get a string of strange characters, again, making me think it's a marshalling/translation issue.
Declare the string parameter as StringBuilder:
[DllImport("winscard.dll"]
public static extern int SCardGetCardTypeProviderName(
IntPtr hContext,
string szCardName,
uint dwProviderId,
StringBuilder szProvider,
ref int pcchProvider
);
....
StringBuilder providerName = new StringBuilder(providerNameLength);
int lReturn = SCardGetCardTypeProviderName(
hSC,
cardName,
SCARD_PROVIDER_CSP,
providerName,
ref providerNameLength
);
No need to repeat the default value of CharSet, and these API functions don't use the Win32 last error mechanism.

C# wrapper class for c++ lib dll

I am trying to create a class in c# to access the function in a c++ lib. The function in the c++ dll :
bool WriteReply(const unsigned char *reply, const unsigned long reply_length).
A sample of how its used in c++:-
unsigned short msg_id = 0x0000;
byte msg_body[] = {(byte)(GetTickCount()/0x100)}; // a random value for loopback data
// combine the message id and message body into an big msg
unsigned long msg_length = sizeof(msg_id)+sizeof(msg_body);
byte* big_msg = new byte[msg_length];
big_msg[0] = LOBYTE(msg_id);
big_msg[1] = HIBYTE(msg_id);
memcpy((void*)&big_msg[2], (void*)msg_body, sizeof(msg_body));
// send the big message
if (!big_dev.WriteReply(big_msg, msg_length))
{
//do something here
}
I can't seem to pass the function from c# to the dll (AccessViolationException). This is the command i've tried:-
byte[] bytearray = new byte[3] { 0x01, 0x02, 0x03 };
IntPtr unmanagedPointer = Marshal.AllocHGlobal(bytearray.Length);
Marshal.Copy(bytearray, 0, unmanagedPointer, bytearray.Length);
bool writestatus = (bool)NativeMethods.WriteReply(unmanagedPointer, (uint)bytearray.Length);
and on the import side:-
[DllImport("dllname.dll", EntryPoint = "WriteReply")]
[return: MarshalAs(UnmanagedType.U1)]
internal static extern bool WriteReply(IntPtr msg, uint reply_length);
Please let me know where have i gone wrong?Thanks!
Assuming your C++ method uses the string and does not modify it...
Try this
__declspec(dllexport) bool __cdecl WriteReply(const unsigned char *reply, const unsigned long reply_length);
[DllImport("libfile.dll", EntryPoint = "WriteReply")]
private static extern bool WriteReplyExternal(
[MarshalAs(UnmanagedType.LPStr)] [Out] string replyString,
[Out] UInt32 replyLength);
Or better yet (since C strings are null-terminated and the buffer is readonly, so you don't have to worry about buffer overflow, the length parameter is redudant):
__declspec(dllexport) bool __cdecl WriteReply(const unsigned char *reply);
[DllImport("libfile.dll", EntryPoint = "WriteReply")]
private static extern bool WriteReplyExternal(
[MarshalAs(UnmanagedType.LPStr)] [Out] string replyString);
These will work if the method is not within a class, otherwise you will need to use the C++ mangled name as the entry point.
If your string contains characters outside the 1...127 ASCII range (e.g. non-English letters), you should use wchar_t instead of char in the C++ and LPWStr instead of LPStr in the marshalling.
Edit:
You need to wrap the private method with another method with a signature that is more appropriate for .NET e.g.
public void WriteReply(string message)
{
var result = WriteReplyExternal(message, message.Length);
if (result == false)
throw new ApplicationException("WriteReplay failed ...");
}
I think the latest addition of code provides a clue as to the real problem:
if (!big_dev.WriteReply(big_msg, msg_length))
This cannot work because WriteReply is an member function. You need to be calling a C style function rather than a C++ member function. The latter requires an instance (big_dev in the code sample).

Calling DLL function with char* param from C#?

I have a C++ dll that I need to call from C#. One of the functions in the dll requires a char* for an input parameter, and another function uses a char* as an output parameter.
What is the proper way to call these from C#?
string should work if the parameter is read-only, if the method modifies the string you should use StringBuilder instead.
Example from reference below:
[DllImport ("libc.so")]
private static extern void strncpy (StringBuilder dest,
string src, uint n);
private static void UseStrncpy ()
{
StringBuilder sb = new StringBuilder (256);
strncpy (sb, "this is the source string", sb.Capacity);
Console.WriteLine (sb.ToString());
}
If you don't know how p/invoke marshaling works you could read http://www.mono-project.com/Interop_with_Native_Libraries
If you are only conserning with strings, read only the section: http://www.mono-project.com/Interop_with_Native_Libraries#Strings
Just using strings will work fine for input parameters, though you can control details about the string with the MarshalAs attribute. E.g.
[DllImport("somedll.dll", CharSet = CharSet.Unicode)]
static extern void Func([MarshalAs(UnmanagedType.LPWStr)] string wideString);
As for returning char* parameters, that's a little more complex since object ownership is involved. If you can change the C++ DLL you can use CoTaskMemAllocate, with something like:
void OutputString(char*& output)
{
char* toCopy = "hello...";
size_t bufferSize = strlen(toCopy);
LPVOID mem = CoTaskMemAlloc(bufferSize);
memcpy(mem, toCopy, bufferSize);
output = static_cast<char*>(mem);
}
The C# side then just uses an 'out string' parameter, and the garbage collector can pick up the ownership of the string.
Another way of doing it would be to use a StringBuilder, but then you need to know how big the string will be before you actually call the function.
Not sure this works, but have you tried with
StringObject.ToCharArray();
Not sure about initialising the String from char * tho. Mybe just assign to a string object, its worth a try.
Have you tried StringBuilder? I found this in a Google search:
[DllImport("advapi32.dll")]
public static extern bool GetUserName(StringBuilder lpBuffer, ref int nSize);
If you post the call you're making we can help you assemble it.
If the DLL function is expecting an allocated buffer of char* (not a wide/multibyte buffer) then the following will work:
[DllImport("somedll.dll", CharSet = CharSet.Ansi)]
static extern void TheFunc(byte[] someBuffer, int someSize);
Here a buffer allocated in c# is passed to TheFunc which fills it with a string of characters (of type char). Bytes aren't "interpreted" by C# they are treated like 8 bit integers, so are perfect for holding 8 bit characters.
An example code snipped would therefore be:
byte[] mybuffer;
int bufSize;
bufSize = 2048;
mybuffer = new byte[bufSize];
TheFunc(mybuffer, bufSize);
string value;
for(value = "", int ix = 0; (mybuffer[ix] != 0) && (ix < bufSize); ix++)
value += (char) mybuffer[ix];
DoSomethingWithTheReturnedString(value);

How to read and modify NTFS Alternate Data Streams using .NET [closed]

Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed 5 years ago.
Improve this question
How can I read and modify "NTFS Alternate Data Streams" using .NET?
It seems there is no native .NET support for it. Which Win32 API's would I use? Also, how would I use them, as I don't think this is documented?
Here is a version for C#
using System.Runtime.InteropServices;
class Program
{
static void Main(string[] args)
{
var mainStream = NativeMethods.CreateFileW(
"testfile",
NativeConstants.GENERIC_WRITE,
NativeConstants.FILE_SHARE_WRITE,
IntPtr.Zero,
NativeConstants.OPEN_ALWAYS,
0,
IntPtr.Zero);
var stream = NativeMethods.CreateFileW(
"testfile:stream",
NativeConstants.GENERIC_WRITE,
NativeConstants.FILE_SHARE_WRITE,
IntPtr.Zero,
NativeConstants.OPEN_ALWAYS,
0,
IntPtr.Zero);
}
}
public partial class NativeMethods
{
/// Return Type: HANDLE->void*
///lpFileName: LPCWSTR->WCHAR*
///dwDesiredAccess: DWORD->unsigned int
///dwShareMode: DWORD->unsigned int
///lpSecurityAttributes: LPSECURITY_ATTRIBUTES->_SECURITY_ATTRIBUTES*
///dwCreationDisposition: DWORD->unsigned int
///dwFlagsAndAttributes: DWORD->unsigned int
///hTemplateFile: HANDLE->void*
[DllImportAttribute("kernel32.dll", EntryPoint = "CreateFileW")]
public static extern System.IntPtr CreateFileW(
[InAttribute()] [MarshalAsAttribute(UnmanagedType.LPWStr)] string lpFileName,
uint dwDesiredAccess,
uint dwShareMode,
[InAttribute()] System.IntPtr lpSecurityAttributes,
uint dwCreationDisposition,
uint dwFlagsAndAttributes,
[InAttribute()] System.IntPtr hTemplateFile
);
}
public partial class NativeConstants
{
/// GENERIC_WRITE -> (0x40000000L)
public const int GENERIC_WRITE = 1073741824;
/// FILE_SHARE_DELETE -> 0x00000004
public const int FILE_SHARE_DELETE = 4;
/// FILE_SHARE_WRITE -> 0x00000002
public const int FILE_SHARE_WRITE = 2;
/// FILE_SHARE_READ -> 0x00000001
public const int FILE_SHARE_READ = 1;
/// OPEN_ALWAYS -> 4
public const int OPEN_ALWAYS = 4;
}
A First, nothing in the Microsoft® .NET Framework provides this functionality. If you want it, plain and simple you'll need to do some sort of interop, either directly or using a third-party library.
If you're using Windows Server™ 2003 or later, Kernel32.dll exposes counterparts to FindFirstFile and FindNextFile that provide the exact functionality you're looking for. FindFirstStreamW and FindNextStreamW allow you to find and enumerate all of the Alternate Data Streams within a particular file, retrieving information about each, including its name and its length. The code for using these functions from managed code is very similar to that which I showed in my December column, and is shown in Figure 1.
Figure 1 Using FindFirstStreamW and FindNextStreamW
[SecurityPermission(SecurityAction.LinkDemand, UnmanagedCode = true)]
public sealed class SafeFindHandle : SafeHandleZeroOrMinusOneIsInvalid {
private SafeFindHandle() : base(true) { }
protected override bool ReleaseHandle() {
return FindClose(this.handle);
}
[DllImport("kernel32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
[ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)]
private static extern bool FindClose(IntPtr handle);
}
public class FileStreamSearcher {
private const int ERROR_HANDLE_EOF = 38;
private enum StreamInfoLevels { FindStreamInfoStandard = 0 }
[DllImport("kernel32.dll", ExactSpelling = true, CharSet = CharSet.Auto, SetLastError = true)]
private static extern SafeFindHandle FindFirstStreamW(string lpFileName, StreamInfoLevels InfoLevel, [In, Out, MarshalAs(UnmanagedType.LPStruct)] WIN32_FIND_STREAM_DATA lpFindStreamData, uint dwFlags);
[DllImport("kernel32.dll", ExactSpelling = true, CharSet = CharSet.Auto, SetLastError = true)] [return: MarshalAs(UnmanagedType.Bool)] private static extern bool FindNextStreamW(SafeFindHandle hndFindFile, [In, Out, MarshalAs(UnmanagedType.LPStruct)] WIN32_FIND_STREAM_DATA lpFindStreamData);
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
private class WIN32_FIND_STREAM_DATA {
public long StreamSize;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 296)]
public string cStreamName;
}
public static IEnumerable<string> GetStreams(FileInfo file) {
if (file == null) throw new ArgumentNullException("file");
WIN32_FIND_STREAM_DATA findStreamData = new WIN32_FIND_STREAM_DATA();
SafeFindHandle handle = FindFirstStreamW(file.FullName, StreamInfoLevels.FindStreamInfoStandard, findStreamData, 0);
if (handle.IsInvalid) throw new Win32Exception();
try {
do {
yield return findStreamData.cStreamName;
} while (FindNextStreamW(handle, findStreamData));
int lastError = Marshal.GetLastWin32Error();
if (lastError != ERROR_HANDLE_EOF) throw new Win32Exception(lastError);
} finally {
handle.Dispose();
}
}
}
You simply call FindFirstStreamW, passing to it the full path to the target file. The second parameter to FindFirstStreamW dictates the level of detail you want in the returned data; currently, there is only one level (FindStreamInfoStandard), which has a numerical value of 0. The third parameter to the function is a pointer to a WIN32_FIND_STREAM_DATA structure (technically, what the third parameter points to is dictated by the value of the second parameter detailing the information level, but as there's currently only one level, for all intents and purposes this is a WIN32_FIND_STREAM_DATA). I've declared that structure's managed counterpart as a class, and in the interop signature I've marked it to be marshaled as a pointer to a struct. The last parameter is reserved for future use and should be 0.
If a valid handle is returned from FindFirstStreamW, the WIN32_FIND_STREAM_DATA instance contains information about the stream found, and its cStreamName value can be yielded back to the caller as the first stream name available. FindNextStreamW accepts the handle returned from FindFirstStreamW and fills the supplied WIN32_FIND_STREAM_DATA with information about the next stream available, if it exists. FindNextStreamW returns true if another stream is available, or false if not.
As a result, I continually call FindNextStreamW and yield the resulting stream name until FindNextStreamW returns false. When that happens, I double check the last error value to make sure that the iteration stopped because FindNextStreamW ran out of streams, and not for some unexpected reason.
Unfortunately, if you're using Windows® XP or Windows 2000 Server, these functions aren't available to you, but there are a couple of alternatives. The first solution involves an undocumented function currently exported from Kernel32.dll, NTQueryInformationFile. However, undocumented functions are undocumented for a reason, and they can be changed or even removed at any time in the future. It's best not to use them. If you do want to use this function, search the Web and you'll find plenty of references and sample source code. But do so at your own risk.
Another solution, and one which I've demonstrated in Figure 2, relies on two functions exported from Kernel32.dll, and these are documented. As their names imply, BackupRead and BackupSeek are part of the Win32® API for backup support:
BOOL BackupRead(HANDLE hFile, LPBYTE lpBuffer, DWORD nNumberOfBytesToRead, LPDWORD lpNumberOfBytesRead, BOOL bAbort, BOOL bProcessSecurity, LPVOID* lpContext);
BOOL BackupSeek(HANDLE hFile, DWORD dwLowBytesToSeek, DWORD dwHighBytesToSeek, LPDWORD lpdwLowByteSeeked, LPDWORD lpdwHighByteSeeked, LPVOID* lpContext);
Figure 2 Using BackupRead and BackupSeek
public enum StreamType {
Data = 1,
ExternalData = 2,
SecurityData = 3,
AlternateData = 4,
Link = 5,
PropertyData = 6,
ObjectID = 7,
ReparseData = 8,
SparseDock = 9
}
public struct StreamInfo {
public StreamInfo(string name, StreamType type, long size) {
Name = name;
Type = type;
Size = size;
}
readonly string Name;
public readonly StreamType Type;
public readonly long Size;
}
public class FileStreamSearcher {
[DllImport("kernel32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool BackupRead(SafeFileHandle hFile, IntPtr lpBuffer, uint nNumberOfBytesToRead, out uint lpNumberOfBytesRead, [MarshalAs(UnmanagedType.Bool)] bool bAbort, [MarshalAs(UnmanagedType.Bool)] bool bProcessSecurity, ref IntPtr lpContext);[DllImport("kernel32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool BackupSeek(SafeFileHandle hFile, uint dwLowBytesToSeek, uint dwHighBytesToSeek, out uint lpdwLowByteSeeked, out uint lpdwHighByteSeeked, ref IntPtr lpContext); public static IEnumerable<StreamInfo> GetStreams(FileInfo file) {
const int bufferSize = 4096;
using (FileStream fs = file.OpenRead()) {
IntPtr context = IntPtr.Zero;
IntPtr buffer = Marshal.AllocHGlobal(bufferSize);
try {
while (true) {
uint numRead;
if (!BackupRead(fs.SafeFileHandle, buffer, (uint)Marshal.SizeOf(typeof(Win32StreamID)), out numRead, false, true, ref context)) throw new Win32Exception();
if (numRead > 0) {
Win32StreamID streamID = (Win32StreamID)Marshal.PtrToStructure(buffer, typeof(Win32StreamID));
string name = null;
if (streamID.dwStreamNameSize > 0) {
if (!BackupRead(fs.SafeFileHandle, buffer, (uint)Math.Min(bufferSize, streamID.dwStreamNameSize), out numRead, false, true, ref context)) throw new Win32Exception(); name = Marshal.PtrToStringUni(buffer, (int)numRead / 2);
}
yield return new StreamInfo(name, streamID.dwStreamId, streamID.Size);
if (streamID.Size > 0) {
uint lo, hi; BackupSeek(fs.SafeFileHandle, uint.MaxValue, int.MaxValue, out lo, out hi, ref context);
}
} else break;
}
} finally {
Marshal.FreeHGlobal(buffer);
uint numRead;
if (!BackupRead(fs.SafeFileHandle, IntPtr.Zero, 0, out numRead, true, false, ref context)) throw new Win32Exception();
}
}
}
}
The idea behind BackupRead is that it can be used to read data from a file into a buffer, which can then be written to the backup storage medium. However, BackupRead is also very handy for finding out information about each of the Alternate Data Streams that make up the target file. It processes all of the data in the file as a series of discrete byte streams (each Alternate Data Stream is one of these byte streams), and each of the streams is preceded by a WIN32_STREAM_ID structure. Thus, in order to enumerate all of the streams, you simply need to read through all of these WIN32_STREAM_ID structures from the beginning of each stream (this is where BackupSeek becomes very handy, as it can be used to jump from stream to stream without having to read through all of the data in the file).
To begin, you first need to create a managed counterpart for the unmanaged WIN32_STREAM_ID structure:
typedef struct _WIN32_STREAM_ID {
DWORD dwStreamId; DWORD dwStreamAttributes;
LARGE_INTEGER Size;
DWORD dwStreamNameSize;
WCHAR cStreamName[ANYSIZE_ARRAY];
} WIN32_STREAM_ID;
For the most part, this is like any other structure you'd marshal through P/Invoke. However, there are a few complications. First and foremost, WIN32_STREAM_ID is a variable-sized structure. Its last member, cStreamName, is an array with length ANYSIZE_ARRAY. While ANYSIZE_ARRAY is defined to be 1, cStreamName is just the address of the rest of the data in the structure after the previous four fields, which means that if the structure is allocated to be larger than sizeof (WIN32_STREAM_ID) bytes, that extra space will in effect be part of the cStreamName array. The previous field, dwStreamNameSize, specifies exactly how long the array is.
While this is great for Win32 development, it wreaks havoc on a marshaler that needs to copy this data from unmanaged memory to managed memory as part of the interop call to BackupRead. How does the marshaler know how big the WIN32_STREAM_ID structure actually is, given that it's variable sized? It doesn't.
The second problem has to do with packing and alignment. Ignoring cStreamName for a moment, consider the following possibility for your managed WIN32_STREAM_ID counterpart:
[StructLayout(LayoutKind.Sequential)]
public struct Win32StreamID {
public int dwStreamId;
public int dwStreamAttributes;
public long Size;
public int dwStreamNameSize;
}
An Int32 is 4 bytes in size and an Int64 is 8 bytes. Thus, you would expect this struct to be 20 bytes. However, if you run the following code, you'll find that both values are 24, not 20:
int size1 = Marshal.SizeOf(typeof(Win32StreamID));
int size2 = sizeof(Win32StreamID); // in an unsafe context
The issue is that the compiler wants to make sure that the values within these structures are always aligned on the proper boundary. Four-byte values should be at addresses divisible by 4, 8-byte values should be at boundaries divisible by 8, and so on. Now imagine what would happen if you were to create an array of Win32StreamID structures. All of the fields in the first instance of the array would be properly aligned. For example, since the Size field follows two 32-bit integers, it would be 8 bytes from the start of the array, perfect for an 8-byte value. However, if the structure were 20-bytes in size, the second instance in the array would not have all of its members properly aligned. The integer values would all be fine, but the long value would be 28 bytes from the start of the array, a value not evenly divisible by 8. To fix this, the compiler pads the structure to a size of 24, such that all of the fields will always be properly aligned (assuming the array itself is).
If the compiler's doing the right thing, you might be wondering why I'm concerned about this. You'll see why if you look at the code in Figure 2. In order to get around the first marshaling issue I described, I do in fact leave the cStreamName out of the Win32StreamID structure. I use BackupRead to read in enough bytes to fill my Win32StreamID structure, and then I examine the structure's dwStreamNameSize field. Now that I know how long the name is, I can use BackupRead again to read in the string's value from the file. That's all well and dandy, but if Marshal.SizeOf returns 24 for my Win32StreamID structure instead of 20, I'll be attempting to read too much data.
To avoid this, I need to make sure that the size of Win32StreamID is in fact 20 and not 24. This can be accomplished in two different ways using fields on the StructLayoutAttribute that adorns the structure. The first is to use the Size field, which dictates to the runtime exactly how big the structure should be:
[StructLayout(LayoutKind.Sequential, Size = 20)]
The second option is to use the Pack field. Pack indicates the packing size that should be used when the LayoutKind.Sequential value is specified and controls the alignment of the fields within the structure. The default packing size for a managed structure is 8. If I change that to 4, I get the 20-byte structure I'm looking for (and as I'm not actually using this in an array, I don't lose efficiency or stability that might result from such a packing change):
[StructLayout(LayoutKind.Sequential, Pack = 4)]
public struct Win32StreamID {
public StreamType dwStreamId;
public int dwStreamAttributes;
public long Size;
public int dwStreamNameSize; // WCHAR cStreamName[1];
}
With this code in place, I can now enumerate all of the streams in a file, as shown here:
static void Main(string[] args) {
foreach (string path in args) {
Console.WriteLine(path + ":");
foreach (StreamInfo stream in FileStreamSearcher.GetStreams(new FileInfo(path))) {
Console.WriteLine("\t{0}\t{1}\t{2}", stream.Name != null ? stream.Name : "(unnamed)", stream.Type, stream.Size);
}
}
}
You'll notice that this version of FileStreamSearcher returns more information than the version that uses FindFirstStreamW and FindNextStreamW. BackupRead can provide data on more than just the primary stream and Alternate Data Streams, also operating on streams containing security information, reparse data, and more. If you only want to see the Alternate Data Streams, you can filter based on the StreamInfo's Type property, which will be StreamType.AlternateData for Alternate Data Streams.
To test this code, you can create a file that has Alternate Data Streams using the echo command at the command prompt:
> echo ".NET Matters" > C:\test.txt
> echo "MSDN Magazine" > C:\test.txt:magStream
> StreamEnumerator.exe C:\test.txt
test.txt:
(unnamed) SecurityData 164
(unnamed) Data 17
:magStream:$DATA AlternateData 18
> type C:\test.txt
".NET Matters"
> more < C:\test.txt:magStream
"MSDN Magazine"
So, now you're able to retrieve the names of all Alternate Data Streams stored in a file. Great. But what if you want to actually manipulate the data in one of those streams? Unfortunately, if you attempt to pass a path for an Alternate Data Stream to one of the FileStream constructors, a NotSupportedException will be thrown: "The given path's format is not supported."
To get around this, you can bypass FileStream's path canonicalization checks by directly accessing the CreateFile function exposed from kernel32.dll (see Figure 3). I've used a P/Invoke for the CreateFile function to open and retrieve a SafeFileHandle for the specified path, without performing any of the managed permission checks on the path, so it can include Alternate Data Stream identifiers. This SafeFileHandle is then used to create a new managed FileStream, providing the required access. With that in place, it's easy to manipulate the contents of an Alternate Data Stream using the System.IO namespace's functionality. The following example reads and prints out the contents of the C:\test.txt:magStream created in the previous example:
string path = #"C:\test.txt:magStream";
using (StreamReader reader = new StreamReader(CreateFileStream(path, FileAccess.Read, FileMode.Open, FileShare.Read))) {
Console.WriteLine(reader.ReadToEnd());
}
Figure 3 Using P/Invoke for CreateFile
private static FileStream CreateFileStream(string path, FileAccess access, FileMode mode, FileShare share) {
if (mode == FileMode.Append) mode = FileMode.OpenOrCreate; SafeFileHandle handle = CreateFile(path, access, share, IntPtr.Zero, mode, 0, IntPtr.Zero);
if (handle.IsInvalid) throw new IOException("Could not open file stream.", new Win32Exception());
return new FileStream(handle, access);
}
[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
private static extern SafeFileHandle CreateFile(string lpFileName, FileAccess dwDesiredAccess, FileShare dwShareMode, IntPtr lpSecurityAttributes, FileMode dwCreationDisposition, int dwFlagsAndAttributes, IntPtr hTemplateFile);
Stephen Toub in MSDN Magazine from January 2006.
There is no native .NET support for them. You have to use P/Invoke to call the native Win32 methods.
To create them, call CreateFile with a path like filename.txt:streamname. If you use the interop call that returns a SafeFileHandle, you can use that to construct a FileStream that you can then read & write to.
To list the streams that exist on a file, use FindFirstStreamW and FindNextStreamW (which exist only on Server 2003 and later - not XP).
I don't believe you can delete a stream, except by copying the rest of the file and leaving off one of the streams. Setting the length to 0 may also work, but I haven't tried it.
You can also have alternate data streams on a directory. You access them the same as with files - C:\some\directory:streamname.
Streams can have compression, encryption and sparseness set on them independent of the default stream.
This nuget package CodeFluent Runtime Client has (amongst other utilities) an NtfsAlternateStream Class that supports create/read/update/delete/enumeration operations.
Not in .NET:
http://support.microsoft.com/kb/105763
#include <windows.h>
#include <stdio.h>
void main( )
{
HANDLE hFile, hStream;
DWORD dwRet;
hFile = CreateFile( "testfile",
GENERIC_WRITE,
FILE_SHARE_WRITE,
NULL,
OPEN_ALWAYS,
0,
NULL );
if( hFile == INVALID_HANDLE_VALUE )
printf( "Cannot open testfile\n" );
else
WriteFile( hFile, "This is testfile", 16, &dwRet, NULL );
hStream = CreateFile( "testfile:stream",
GENERIC_WRITE,
FILE_SHARE_WRITE,
NULL,
OPEN_ALWAYS,
0,
NULL );
if( hStream == INVALID_HANDLE_VALUE )
printf( "Cannot open testfile:stream\n" );
else
WriteFile(hStream, "This is testfile:stream", 23, &dwRet, NULL);
}

Categories