I want to write a program that shows the files of another drive with hard links.
I want to keep both hardlinks consistent in filename and other things, so I have to get a function/method where I can list all current hard links of a file.
For example:
I have a file C:\file.txt and a second hard link to D:\file.txt.
Then I rename D:\file.txt to D:\file_new.txt.
I now want to be able to also rename the hardlink on the C drive as well.
So I need a function which returns for D:\file_new.txt that there are the following hardlinks:
C:\file.txt
D:\file_new.txt
then I can rename the hard link on C:\ also to get D:\file_new.txt
So I need to get all hard links of a physical file.
Or: All hard links of a file addressed with a hard link.
Hope somebody can help!
Edit:
Oliver noticed that hard links can't be used on different disks. thanks... So I extend the question to: What do I need? Junction Points? Symbolic Links? It should also work with files not only with folders!
the following code should work well (originally postet by Peter provost on PowerShell Code Repository):
using System;
using System.Collections.Generic;
using System.IO;
using System.Runtime.InteropServices;
using System.Text;
using Microsoft.Win32.SafeHandles;
using FILETIME = System.Runtime.InteropServices.ComTypes.FILETIME;
namespace HardLinkEnumerator
{
public static class Kernel32Api
{
[StructLayout(LayoutKind.Sequential)]
public struct BY_HANDLE_FILE_INFORMATION
{
public uint FileAttributes;
public FILETIME CreationTime;
public FILETIME LastAccessTime;
public FILETIME LastWriteTime;
public uint VolumeSerialNumber;
public uint FileSizeHigh;
public uint FileSizeLow;
public uint NumberOfLinks;
public uint FileIndexHigh;
public uint FileIndexLow;
}
[DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)]
static extern SafeFileHandle CreateFile(
string lpFileName,
[MarshalAs(UnmanagedType.U4)] FileAccess dwDesiredAccess,
[MarshalAs(UnmanagedType.U4)] FileShare dwShareMode,
IntPtr lpSecurityAttributes,
[MarshalAs(UnmanagedType.U4)] FileMode dwCreationDisposition,
[MarshalAs(UnmanagedType.U4)] FileAttributes dwFlagsAndAttributes,
IntPtr hTemplateFile);
[DllImport("kernel32.dll", SetLastError = true)]
static extern bool GetFileInformationByHandle(SafeFileHandle handle, out BY_HANDLE_FILE_INFORMATION lpFileInformation);
[DllImport("kernel32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
static extern bool CloseHandle(SafeHandle hObject);
[DllImport("kernel32.dll", SetLastError=true, CharSet=CharSet.Unicode)]
static extern IntPtr FindFirstFileNameW(
string lpFileName,
uint dwFlags,
ref uint stringLength,
StringBuilder fileName);
[DllImport("kernel32.dll", SetLastError = true, CharSet=CharSet.Unicode)]
static extern bool FindNextFileNameW(
IntPtr hFindStream,
ref uint stringLength,
StringBuilder fileName);
[DllImport("kernel32.dll", SetLastError = true)]
static extern bool FindClose(IntPtr fFindHandle);
[DllImport("kernel32.dll")]
static extern bool GetVolumePathName(string lpszFileName,
[Out] StringBuilder lpszVolumePathName, uint cchBufferLength);
[DllImport("shlwapi.dll", CharSet = CharSet.Auto)]
static extern bool PathAppend([In, Out] StringBuilder pszPath, string pszMore);
public static int GetFileLinkCount(string filepath)
{
int result = 0;
SafeFileHandle handle = CreateFile(filepath, FileAccess.Read, FileShare.Read, IntPtr.Zero, FileMode.Open, FileAttributes.Archive, IntPtr.Zero);
BY_HANDLE_FILE_INFORMATION fileInfo = new BY_HANDLE_FILE_INFORMATION();
if (GetFileInformationByHandle(handle, out fileInfo))
result = (int)fileInfo.NumberOfLinks;
CloseHandle(handle);
return result;
}
public static string[] GetFileSiblingHardLinks(string filepath)
{
List<string> result = new List<string>();
uint stringLength = 256;
StringBuilder sb = new StringBuilder(256);
GetVolumePathName(filepath, sb, stringLength);
string volume = sb.ToString();
sb.Length = 0; stringLength = 256;
IntPtr findHandle = FindFirstFileNameW(filepath, 0, ref stringLength, sb);
if (findHandle.ToInt32() != -1)
{
do
{
StringBuilder pathSb = new StringBuilder(volume, 256);
PathAppend(pathSb, sb.ToString());
result.Add(pathSb.ToString());
sb.Length = 0; stringLength = 256;
} while (FindNextFileNameW(findHandle, ref stringLength, sb));
FindClose(findHandle);
return result.ToArray();
}
return null;
}
}
}
Maybe i misunderstand your questions, but hardlinks can't go from one drive to another. They can only exist on a single drive.
Within the .Net framwork there is no support to get these informations. But the Win32 API can provide you with these informations.
Take a look at this article. It may help you.
Update
As far as i know it is not possible to do it between different drives. Junction Points are definitely not your friend cause it only works on foldes. But after reading this wikipedia article it seems that you can do it on Vista and Win7 with symbolic links. There is also a link to this shell extension which seems to cover everything you can do with these NTFS special features. Maybe with this you can check if your goal is reachable and maybe afterwards check the MSDN for the desired Win32 API function.
Note:
Hard links can only be files on the same volume, which contradicts the requirements of the question, which led to a follow-up question in the question body that the OP himself answered.
Given the title of the question, however, users who find this post by googling are most likely interest in a solution to the problem as stated in the title: given a file, how can I find all hard links to it (which are by definition all on the same volume).
The solution below is a streamlined and modernized adaptation of Marcel Nolte's helpful answer.
Its behavior and constraints are:
For a given input file, its array of hard links is returned, as full file paths, which includes the input file's path itself.
If the file has only one hard link (itself), or you specify a directory, only that file's / directory's full path is returned.
If the path refers to a volume that doesn't support hard links, or the path doesn't exist, null is returned.
NiKiZe notes that you cannot query hardlinks via a CIFS/SMB connection (network drive).
The following is a self-contained Windows console application that you should be able to compile and run as-is; the method of interest is HardLinkHelper.GetHardLinks():
using System;
using System.Text;
using System.Collections.Generic;
using System.Runtime.InteropServices;
namespace demo
{
public static class Program
{
public static void Main()
{
// Sample file that is known to have (one) hard link.
var file = Environment.ExpandEnvironmentVariables(#"%SYSTEMROOT%\explorer.exe");
foreach (var link in HardLinkHelper.GetHardLinks(file) ?? new string[] { "n/a" })
{
Console.WriteLine(link);
}
}
}
public static class HardLinkHelper
{
#region WinAPI P/Invoke declarations
[DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
static extern IntPtr FindFirstFileNameW(string lpFileName, uint dwFlags, ref uint StringLength, StringBuilder LinkName);
[DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
static extern bool FindNextFileNameW(IntPtr hFindStream, ref uint StringLength, StringBuilder LinkName);
[DllImport("kernel32.dll", SetLastError = true)]
static extern bool FindClose(IntPtr hFindFile);
[DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
static extern bool GetVolumePathName(string lpszFileName, [Out] StringBuilder lpszVolumePathName, uint cchBufferLength);
public static readonly IntPtr INVALID_HANDLE_VALUE = (IntPtr)(-1); // 0xffffffff;
public const int MAX_PATH = 65535; // Max. NTFS path length.
#endregion
/// <summary>
//// Returns the enumeration of hardlinks for the given *file* as full file paths, which includes
/// the input path itself.
/// </summary>
/// <remarks>
/// If the file has only one hardlink (itself), or you specify a directory, only that
/// file's / directory's full path is returned.
/// If the path refers to a volume that doesn't support hardlinks, or the path
/// doesn't exist, null is returned.
/// </remarks>
public static string[] GetHardLinks(string filepath)
{
StringBuilder sbPath = new StringBuilder(MAX_PATH);
uint charCount = (uint)sbPath.Capacity; // in/out character-count variable for the WinAPI calls.
// Get the volume (drive) part of the target file's full path (e.g., #"C:\")
GetVolumePathName(filepath, sbPath, (uint)sbPath.Capacity);
string volume = sbPath.ToString();
// Trim the trailing "\" from the volume path, to enable simple concatenation
// with the volume-relative paths returned by the FindFirstFileNameW() and FindFirstFileNameW() functions,
// which have a leading "\"
volume = volume.Substring(0, volume.Length - 1);
// Loop over and collect all hard links as their full paths.
IntPtr findHandle;
if (INVALID_HANDLE_VALUE == (findHandle = FindFirstFileNameW(filepath, 0, ref charCount, sbPath))) return null;
List<string> links = new List<string>();
do
{
links.Add(volume + sbPath.ToString()); // Add the full path to the result list.
charCount = (uint)sbPath.Capacity; // Prepare for the next FindNextFileNameW() call.
} while (FindNextFileNameW(findHandle, ref charCount, sbPath));
FindClose(findHandle);
return links.ToArray();
}
}
}
I found a solution:
First I don't have to use hard links (since they can't point to an other disk). I have to use symbolic links instead. So I have one hard linked file on the original disk and symbolic links on other disks to this file. The limitation is OS must be Vista or newer.
Second I have to be able to find out where the symbolic link is pointing to.
Here I found a good example how to find out the information I need:
http://www.codeproject.com/KB/vista/ReparsePointID.aspx
The only thing I don't managed is to find all symbolic links from a specific file (hard link). I guess there is no out of the box solution and I have to recurse all symbolic links and test the target. But in my case that's no problem.
I hope that can help others!
I used the HardLinkHelper class in a project, and it works great for finding hard links on Windows NTFS drives.
Here's my version of HardLinkHelper class with the following changes:
Does not use StringBuilder, because Microsoft recommends to avoid using StringBuilder on pinvokes.
It has the member variables (INVALID_HANDLE_VALUE & MAX_PATH) private.
Null is never returned, and instead empty list is returned for non-existing path or unsupported path. This allows safe use of foreach on return value.
Added ReturnEmptyListIfOnlyOne input variable which when set to true, allows calling function to use it in a foreach, where the foreach loop will only be entered if the file has multiple shared hard links.
Example usage:
foreach (var link in HardLinkHelper.GetHardLinks(entry.Path, true))
public static class HardLinkHelper {
#region WinAPI P/Invoke declarations
[DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
static extern IntPtr FindFirstFileNameW(string lpFileName, uint dwFlags, ref uint StringLength, char[] LinkName);
[DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
static extern bool FindNextFileNameW(IntPtr hFindStream, ref uint StringLength, char[] LinkName);
[DllImport("kernel32.dll", SetLastError = true)]
static extern bool FindClose(IntPtr hFindFile);
[DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
static extern bool GetVolumePathName(string lpszFileName, [Out] char[] lpszVolumePathName, uint cchBufferLength);
private static readonly IntPtr INVALID_HANDLE_VALUE = (IntPtr)(-1); // 0xffffffff;
private const int MAX_PATH = 65535; // Max. NTFS path length.
#endregion
/// <summary>
/// Checks for hard links on a Windows NTFS drive associated with the given path.
/// </summary>
/// <param name="filepath">Fully qualified path of the file to check for shared hard links</param>
/// <param name="ReturnEmptyListIfOnlyOne">Set true, to return populated list only for files having multiple hard links</param>
/// <returns>
/// Empty list is returned for non-existing path or unsupported path.
/// Single hard link paths returns empty list if ReturnEmptyListIfOnlyOne is true. If false, returns single item list.
/// For multiple shared hard links, returns list of all the shared hard links.
/// </returns>
public static List<string> GetHardLinks(string filepath, bool ReturnEmptyListIfOnlyOne = false) {
List<string> links = new List<string>();
try {
Char[] sbPath = new Char[MAX_PATH + 1];
uint charCount = (uint)MAX_PATH;
GetVolumePathName(filepath, sbPath, (uint)MAX_PATH); // Must use GetVolumePathName, because Path.GetPathRoot fails on a mounted drive on an empty folder.
string volume = new string(sbPath).Trim('\0');
volume = volume.Substring(0, volume.Length - 1);
Array.Clear(sbPath, 0, MAX_PATH); // Reset the array because these API's can leave garbage at the end of the buffer.
IntPtr findHandle;
if (INVALID_HANDLE_VALUE != (findHandle = FindFirstFileNameW(filepath, 0, ref charCount, sbPath))) {
do {
links.Add((volume + new string(sbPath)).Trim('\0')); // Add the full path to the result list.
charCount = (uint)MAX_PATH;
Array.Clear(sbPath, 0, MAX_PATH);
} while (FindNextFileNameW(findHandle, ref charCount, sbPath));
FindClose(findHandle);
}
}
catch (Exception ex) {
//Logger.Instance.Info($"GetHardLinks: Exception, file: {filepath}, reason: {ex.Message}, stacktrace {ex.StackTrace}");
}
if (ReturnEmptyListIfOnlyOne && links.Count < 2)
links.Clear();
return links;
}
}
try:
using System.IO;
string[] filePathsC = Directory.GetFiles(#"c:\");
string[] filePathsD = Directory.GetFiles(#"d:\");
and loop through the arrays, find the files and change the name
EDIT:
By reading the comments I know that I answered before I knew what a hardlink is. I realise now that this answer is not helping.
Related
I have some code which retrieves the 128bit NTFS Ids from files at specific paths. Then I attempted to retrieve the file path using this ID. The code works as long as when retrieving the paths I run as admin. This is not going to be possible in production. Unfortunately I am unable to call Marshal.GetLastWin32Error() because the System.AccessViolationException causes the application to completely crash. Below is the code to retrieve the paths.
public const int NO_PERMISSION = 0;
[DllImportAttribute("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
public static extern SafeFileHandle CreateFile(
string lpFileName,
uint dwDesiredAccess,
uint dwShareMode,
[InAttribute()] System.IntPtr lpSecurityAttributes,
uint dwCreationDisposition,
uint dwFlagsAndAttributes,
[InAttribute()] System.IntPtr hTemplateFile
);
[DllImportAttribute("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
public static extern SafeFileHandle OpenFileById(
IntPtr hVolumeHint,
FILE_ID_DESCRIPTOR lpFileId,
uint dwDesiredAccess,
uint dwShareMode,
[InAttribute()] System.IntPtr lpSecurityAttributes,
uint dwFlagsAndAttributes
);
public enum _FILE_ID_TYPE
{
FileIdType = 0,
ObjectIdType,
ExtendedFileIdType,
MaximumFileIdType
}
[StructLayout(LayoutKind.Explicit)]
public struct FILE_ID_128
{
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 16)]
[FieldOffset(0)]
public byte[] Identifier;
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
public struct FILE_ID_DESCRIPTOR
{
public uint dwSize;
public _FILE_ID_TYPE Type;
public FILE_ID_128 ExtendedFileId;
}
public static string GetObjectPathFromId(string pathToSection, string hexId)
{
// We need a file handle to the drive we are looking in
using (SafeFileHandle handle = Methods.CreateFile(
pathToSection,
Constants.NO_PERMISSION,
Constants.NO_PERMISSION,
IntPtr.Zero,
Constants.OPEN_EXISTING,
0x02000000 | 0x00000080,
IntPtr.Zero))
{
// Build descriptor
FILE_ID_DESCRIPTOR descriptor = new FILE_ID_DESCRIPTOR();
descriptor.dwSize = (uint)Marshal.SizeOf(descriptor);
descriptor.Type = _FILE_ID_TYPE.ExtendedFileIdType;
descriptor.ExtendedFileId.Identifier = StringToByteArrayFastest(hexId);
using (SafeFileHandle actualFile = OpenFileById(handle.DangerousGetHandle(), descriptor,
Constants.NO_PERMISSION, Constants.NO_PERMISSION,
IntPtr.Zero, 0))
{
if (actualFile.IsInvalid)
return "";
// Buffer for the path, this should be way big enough
int sizeOfBuffer = 1024;
// Allocate a buffer
IntPtr pointer = Marshal.AllocHGlobal(sizeOfBuffer);
uint size = (uint)sizeOfBuffer;
uint returnValue = GetFinalPathNameByHandleW(actualFile.DangerousGetHandle(), pointer, size, 0);
// Copy it into a managed array
byte[] outPut = new byte[sizeOfBuffer];
Marshal.Copy(pointer, outPut, 0, (int)returnValue);
// Decode it
var str = Encoding.Unicode.GetString(outPut);
// Will be an empty string if the call fails
return str;
}
}
}
Again I want to specify - this code works perfectly when running as admin. The files are owned by the user, the user is able to delete, rename and move the files without any additional permissions.
Any help would be greatly appreciated thanks!
Edit1:
I implemented the answer found here How to handle AccessViolationException to successfully catch the exception. However even after doing this Marshal.GetLastWin32Error() returns 0. If anyone has any idea of how I can debug this type of issue please let me know.
Also it's still functioning when I run as admin, just not as a user.
Edit2:
Not sure if it's relevant - library with this code is building for .NET Standard 2.0 - Application using this library code is building for .NET Framework 4.6.2
I'm attempting to write a simple program that would take a CSV consisting of two columns, and given a keyword from the first column, return the corresponding value from the second one. The problem is that I need the CSV to be in an alternate data stream to make the program as portable as possible (I want to make it so that when the user drops a CSV file on the executable, the CSV is overwritten). That is why I'm trying to use WinAPI's CreateFile function -- .NET doesn't support alternate data streams. Unfortunately, I'm failing miserably.
In the current state, the program is supposed to read a file named test.csv. I want to do CreateFile on it, convert the intPtr handle to a SafeFileHandle and then pass the SafeFileHandle to a FileStream constructor. The best I've been able to achieve is an empty stream though. It doesn't seem to me that the program is actually getting the right handle. When I try "CREATE_ALWAYS" or "CREATE_NEW" instead of "OPEN_ALWAYS", I'm getting an "Invalid Handle" error, no matter what I do with the rest of the parameters. With "OPEN_ALWAYS", when I check the value of "stream.Name", I'm getting "Unknown".
Here is the code:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.IO;
using System.Runtime.InteropServices;
using Microsoft.Win32.SafeHandles;
namespace Searcher
{
public partial class Searcher : Form
{
public static List<string> keywords;
public static List<string> values;
public Searcher()
{
InitializeComponent();
//byte[] path = Encoding.UTF8.GetBytes("C:\\Users\\as\\Documents\\test.csv");
SafeFileHandle safeADSHandle = NativeMethods.CreateFileW("test.csv",
NativeConstants.GENERIC_READ,
NativeConstants.FILE_SHARE_READ,
IntPtr.Zero,
NativeConstants.OPEN_ALWAYS,
0,
IntPtr.Zero);
//var safeADSHandle = new SafeFileHandle(handle, true);
if (safeADSHandle.IsInvalid)
{
Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error());
}
var stream = new FileStream(safeADSHandle, FileAccess.Read);
MessageBox.Show(stream.Name);
var reader = new StreamReader(stream);
Searcher.keywords = new List<string>();
Searcher.values = new List<string>();
while (!reader.EndOfStream)
{
var line = reader.ReadLine();
var values = line.Split(',');
Searcher.keywords.Add(values[0]);
Searcher.values.Add(values[1]);
}
cbKeyword.DataSource = Searcher.keywords;
cbKeyword.AutoCompleteSource = AutoCompleteSource.ListItems;
}
private void btnSearch_Click(object sender, EventArgs e)
{
tbResult.Text = Searcher.values[cbKeyword.SelectedIndex];
}
}
}
public partial class NativeMethods
{
[DllImportAttribute("kernel32.dll", SetLastError = true, EntryPoint = "CreateFile")]
public static extern SafeFileHandle 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;
public const uint GENERIC_READ = 2147483648;
/// 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;
public const int CREATE_NEW = 1;
}
Edit I've changed the code above slightly to reflect the changes after the comments. Now I'm not converting from IntPtr to SafeFileHandle. One thing to mention perhaps is that I before this change I tried reading the value of handle.ToString() to see if it was changing and it was -- it's a random number.
The reason this is not working is because you have specified an entry point without a character set. When you don't specify a character set in DllImport, the default is CharSet.Ansi, which means platform invoke will search for the function as follows:
First for an unmangled exported function, i.e. the name CreateFile from the entry point that you specified (which does not exist in kernel32.dll)
Next if the unmangled name was not found, it will search for the mangled name based on the charset, so it will search for CreateFileA
So it will find the CreateFileA exported function, which assumes any strings passed in to it are in 1-byte character ANSI format. However, you are marshalling the string as a wide string. Note that the wide-character version of the function is called CreateFileW (this distinction between ANSI and wide character versions of functions is common in the Windows API).
To fix this you just need to make sure that the marshalling of your parameters matches up with what the function you are importing expects. So you could remove the EntryPoint field, in which case it will use the C# method name to find the exported function instead (so it will find CreateFileW).
However to make it even clearer, I would write your platform invoke code as follows:
[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern IntPtr CreateFile(
[MarshalAs(UnmanagedType.LPTStr)] string filename,
[MarshalAs(UnmanagedType.U4)] FileAccess access,
[MarshalAs(UnmanagedType.U4)] FileShare share,
IntPtr securityAttributes,
[MarshalAs(UnmanagedType.U4)] FileMode creationDisposition,
[MarshalAs(UnmanagedType.U4)] FileAttributes flagsAndAttributes,
IntPtr templateFile);
I took this from the pinvoke.net website, which should be your go-to for writing pinvoke code rather than trying to bash it out yourself (especially if you aren't familiar with the Windows API or marshalling :).
The reason there was no error is probably because CreateFile was creating the file since it wouldn't have been able to find it.
The file name will appear as "[Unknown]" though. I suspect this is because the code to get a file name from a handle is non-trivial.
I am trying to find the COM port assigned to a USB device, through the registry using Silverlight, and have tried the following:
dynamic WshShell = AutomationFactory.CreateObject("WScript.Shell");
string strRegKeyUSB = #"HKLM\HARDWARE\DEVICEMAP\SERIALCOMM\\Device\USB_COM";
string strCOMValue = WshShell.RegRead(strRegKeyUSB);
This approach usually works 100%, but all Value names under the DEVICEMAP Key is "\Device\XXX"
This causes the the "Path" to not be found, as the "\\" between SERIALCOMM and Device is not seen as valid (Throws Error: "Cannot find File Specified")
This, as far as I can see, only really leaves me with one option - P/Invoke, in Silverlight 5
I am using P/Invoke already for a SerialWrapper Class, to Open, Read, Write the COM Ports, and would like to include only the minimal needed to only read this one Key Value from the Registry - I have tried following some examples I have found, but not being strong in Interop, P/Invoke, etc. I am struggling to find only the portions I need.
If someone could please just give me a basic example, to only accomplish this (I do NOT need to write to the registry, or read QWORDS, or anything else - Only read this string value from only this specific key)
I have tried following the following post (Marshal.PtrToStructure in Silverlight) and it's answer, in relation to this (http://www.pinvoke.net/default.aspx/winspool.enumports), but have not been able to get this working, Most likely form a lack of REALLY understanding ;-)
Here's a simple desktop application that reads a REG_SZ value. It's crude and simple. It will read the value that you want. You may have to adapt it to Silverlight. I cannot help you there!
I hope this is useful:
using System;
using System.ComponentModel;
using System.Runtime.InteropServices;
using System.Text;
namespace ConsoleApplication1
{
internal static class NativeMethods
{
public const int ERROR_SUCCESS = 0;
public const uint HKEY_LOCAL_MACHINE = 0x80000002;
public const int KEY_READ = 0x20019;
[DllImport("advapi32.dll", CharSet = CharSet.Unicode)]
public static extern int RegOpenKeyEx(
UIntPtr hKey,
string subKey,
int ulOptions,
int samDesired,
out UIntPtr hkResult
);
[DllImport("advapi32.dll")]
public static extern int RegCloseKey(
UIntPtr hKey
);
[DllImport("advapi32.dll", CharSet = CharSet.Unicode)]
public static extern int RegQueryValueEx(
UIntPtr hKey,
string lpValueName,
int lpReserved,
IntPtr type,
IntPtr lpData,
ref int lpcbData
);
}
internal static class RegistryWrapper
{
private static void checkErrorCode(int errorCode)
{
if (errorCode != NativeMethods.ERROR_SUCCESS)
throw new Win32Exception(errorCode);
}
public static string ReadRegString(UIntPtr rootKey, string subKey, string name)
{
UIntPtr hkey;
checkErrorCode(NativeMethods.RegOpenKeyEx(rootKey, subKey, 0, NativeMethods.KEY_READ, out hkey));
try
{
int cbData = 0;
checkErrorCode(NativeMethods.RegQueryValueEx(hkey, name, 0, IntPtr.Zero, IntPtr.Zero, ref cbData));
IntPtr ptr = Marshal.AllocHGlobal(cbData);
try
{
checkErrorCode(NativeMethods.RegQueryValueEx(hkey, name, 0, IntPtr.Zero, ptr, ref cbData));
return Marshal.PtrToStringUni(ptr, cbData / sizeof(char)).TrimEnd('\0');
}
finally
{
Marshal.FreeHGlobal(ptr);
}
}
finally
{
checkErrorCode(NativeMethods.RegCloseKey(hkey));
}
}
}
class Program
{
static void Main(string[] args)
{
Console.WriteLine(RegistryWrapper.ReadRegString((UIntPtr)NativeMethods.HKEY_LOCAL_MACHINE, #"HARDWARE\DEVICEMAP\SERIALCOMM", #"\Device\Serial0"));
}
}
}
Update
It seems that AllocHGlobal and FreeHGlobal are not available on Silverlight. You can p/invoke to LocalAlloc and LocalFree instead. Or you could use CoTaskMemAlloc and CoTaskMemFree. Here's what the former looks like:
[DllImport("kernel32.dll", SetLastError=true)]
static extern IntPtr LocalAlloc(uint uFlags, UIntPtr uBytes);
[DllImport("kernel32.dll", SetLastError=true)]
static extern IntPtr LocalFree(IntPtr hMem);
Define LMEM_FIXED like this:
const uint LMEM_FIXED = 0x0000;
Then replace the call to AllocHGlobal with this:
IntPtr ptr = LocalAlloc(LMEM_FIXED, cbData);
And replace the call to FreeHGlobal with this:
LocalFree(ptr);
A BIG thank you to #Dave Heffernan,
I got this to work FINALLY...
I Added the following code within the RegistryWrapper class in Dave's Answer:
[DllImport("kernel32.dll", SetLastError = true)]
static extern IntPtr LocalAlloc(uint uFlags, int size);
internal static IntPtr AllocHGlobal(int size)
{
uint LPTR = (uint)0x0040;
IntPtr hGlobal = LocalAlloc(LPTR, size);
if (hGlobal == IntPtr.Zero)
{
throw new OutOfMemoryException("Unmanaged memory was not allocated.");
}
return hGlobal;
}
This works around the limitation of Marshal.AllocHGlobal not being available in Silverlight.
I then also just changed the reference to Marshal.AllocHGlobal to the local AllocHGlobal method above.
According to undocprint given a job ID it should be possible to retrieve the spool file for the job using OpenPrinter and ReadPrinter by opening the printer using a string with format "PrinterName,Job xxxx". The MSDN documentation lists this method as well, though with an additional space after the comma "PrinterName, Job xxxx".
Whenever I try to call this method from my test application (using either string format) I get ERROR_ACCESS_DENIED (Windows 8 x64). Why is this and what do I need to do to get this working?
I'm running the test app as admin and have no trouble pausing jobs or printers or accessing other information.
I know the ID I'm using is valid because for an invalid ID it returns ERROR_INVALID_PRINTER_NAME instead.
The code I'm using:
public static void OpenPrinter(String printerName,
ref IntPtr printerHandle,
ref PRINTER_DEFAULTS defaults) {
if (OpenPrinter(printerName, ref printerHandle, ref defaults) == 0) {
throw new Win32Exception(Marshal.GetLastWin32Error(),
string.Format("Error getting access to printer: {0}", printerName));
}
}
[DllImport("winspool.drv", EntryPoint = "OpenPrinterW", SetLastError = true, CharSet = CharSet.Unicode,
ExactSpelling = true, CallingConvention = CallingConvention.StdCall)]
public static extern int OpenPrinter(String pPrinterName, ref IntPtr phPrinter, ref PRINTER_DEFAULTS pDefault);
[StructLayout(LayoutKind.Sequential)]
public struct PRINTER_DEFAULTS {
public IntPtr pDatatype;
public IntPtr pDevMode;
public uint DesiredAccess;
}
Turns out that pDefaults must be passed NULL and then everything works.
This requires changing the extern definition to take an IntPtr or similar.
I haven't seen any documentation about why this might be (in fact the MSDN docs state that this passing NULL should be the same as requesting USE access), but it definitely fixes the issue in our testing.
Permissions. Are you running with administrator rights?
I'm not very good with P/Invoke. Can anyone tell me how to declare and use the following shell32.dll function in .NET?
From http://msdn.microsoft.com/en-us/library/bb762230%28VS.85%29.aspx:
HRESULT SHMultiFileProperties(
IDataObject *pdtobj,
DWORD dwFlags
);
It is for displaying the Windows Shell Properties dialog for multiple file system objects.
I already figured out how to use SHObjectProperties for one file or folder:
[DllImport("shell32.dll", SetLastError = true)]
static extern bool SHObjectProperties(uint hwnd, uint shopObjectType, [MarshalAs(UnmanagedType.LPWStr)] string pszObjectName, [MarshalAs(UnmanagedType.LPWStr)] string pszPropertyPage);
public static void ShowDialog(Form parent, FileSystemInfo selected)
{
SHObjectProperties((uint)parent.Handle, (uint)ObjectType.File, selected.FullName, null));
}
enum ObjectType
{
Printer = 0x01,
File = 0x02,
VoumeGuid = 0x04,
}
Can anyone help?
There's an IDataObject interface and a DataObject class in the .NET Framework.
[DllImport("shell32.dll", SetLastError = true)]
static extern int SHMultiFileProperties(IDataObject pdtobj, int flags);
public static void Foo()
{
var pdtobj = new DataObject();
pdtobj.SetFileDropList(new StringCollection { #"C:\Users", #"C:\Windows" });
if (SHMultiFileProperties(pdtobj, 0) != 0 /*S_OK*/)
{
throw new Win32Exception();
}
}
EDIT: I've just compiled and tested this and it works (pops up some dialog with folder appearance settings).
I maybe reading you question incorrectly, but I think you are looking for the extended file properties for files. i.e. opening windows explorer and viewing columns like attributes, owner, copyright, size, date created etc?
There is an API in Shell32 called GetDetailsOf that will provide this information. A starting article on codeproject
Cheers,
John