P/Invoke for shell32.dll's SHMultiFileProperties - c#

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

Related

How to check if the recycle bin is empty

If possible, I want to be able to check whether or not the recycle bin is empty, with minimal hassle (importing dlls, importing anything, creating entire new class to hold recycle bin functionality etc...)
I already have the code below that I found online to empty the recycle bin so it seems natural to suspect that I should be able to extend this to check if it needs emptying first, perhaps.. another function within Shell32.dll.
enum BinFlags : uint
{
SHERB_NOCONFIRMATION = 0x00000001,
SHERB_NOPROGRESSUI = 0x00000002,
SHERB_NOSOUND = 0x00000004
}
[DllImport("Shell32.dll", CharSet = CharSet.Unicode)]
static extern uint SHEmptyRecycleBin(IntPtr hwnd, string rootPath,
BinFlags flags);
/* snip, bunch of code... */
SHEmptyRecycleBin(IntPtr.Zero, null, 0);
You can add reference to the C:\Windows\System32\Shell32.dll and use the following snippet:
Shell shell = new Shell();
Folder recycleBin = shell.NameSpace(10);
int itemsCount = recycleBin.Items().Count;
Taken from here.
It's poor documentation, but you might want SHQueryRecycleBin EDIT: Slightly better documentation over at MSDN.
[DllImport("shell32.dll")]
static extern int SHQueryRecycleBin(string pszRootPath, ref SHQUERYRBINFO
pSHQueryRBInfo);
[StructLayout(LayoutKind.Sequential, Pack=4)]
public struct SHQUERYRBINFO
{
public int cbSize;
public long i64Size;
public long i64NumItems;
}
It looks like you make the call and it fills the object and if you look at i64NumItems and it is 0 then the recycle bin is empty.
public static int GetCount()
{
SHQUERYRBINFO sqrbi = new SHQUERYRBINFO();
sqrbi.cbSize = Marshal.SizeOf(typeof(SHQUERYRBINFO));
int hresult = SHQueryRecycleBin(string.Empty, ref sqrbi);
return (int)sqrbi.i64NumItems;
}

Why do I get ERROR_ACCESS_DENIED attempting to open a specific job using OpenPrinter?

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?

Can't figure out how to check if Biometric is present

At work we make our own tablets. Some of the tablets have fingerprint biometrics, some don't. Sometimes a tech forgets to plug it in. I have yet to find a way to check if that device (or any for that matter) is present.
My first approach was to use the GUID for a biometric which is {53D29EF7-377C-4D14-864B-EB3A85769359}. I would search in the registry at hklm\system\currontcontrolset\control\class and check to see if that key is present.
That doesn't work because it seems that Windows 7 has that key present even if you've never had a biometric installed. It worked in XP, but I just tried again on a unit that used to have a biometric but I took it out and that key is still present.
The hardest part about this problem is that I have to work with Windows 7, 7 embedded, xp, and xp embedded.
Next idea was to use WMI, but I couldn't find the correct class to call to check if it is present.
I then found a biometric.dll but that only works in Windows 7.
Sometimes finding a common solution to a problem is not always easy. I'm doing this project in C# but iI'm willing to convert it to any language.
Any ideas on were I should start looking?
With the help of Joshua Drake who gave me an awesome link on how to solve my problem, those are my results:
The code that I am fixing to post is kind of specialized in that it looks for a specific GUID and only looks for the first one. I adapted it from the article about how to disable a device, although this code does not disable anything it merely checks for presence.
public static bool IsDevicePresent(string guid)
{
var info = IntPtr.Zero;
var NullGuid = new Guid(guid);
try
{
info = SetupDiGetClassDevsW(ref NullGuid,null,IntPtr.Zero,DIGCF_PRESENT);
CheckError("SetupDiGetClassDevs");
var devdata = new SP_DEVINFO_DATA();
devdata.cbSize = (UInt32)Marshal.SizeOf(devdata);
// Get first device matching device criterion.
SetupDiEnumDeviceInfo(info,0,out devdata);
// if no items match filter, throw
if (Marshal.GetLastWin32Error() == ERROR_NO_MORE_ITEMS)
CheckError("No device found matching filter.", 0xcffff);
CheckError("SetupDiEnumDeviceInfo");
}
catch
{
return false;
}
finally
{
if (info != IntPtr.Zero)
SetupDiDestroyDeviceInfoList(info);
}
return true;
}
private static void CheckError(string message, int lasterror = -1)
{
int code = lasterror == -1 ? Marshal.GetLastWin32Error() : lasterror;
if (code != 0)
throw new ApplicationException(String.Format("Error disabling hardware device (Code {0}): {1}",code, message));
}
[DllImport("setupapi.dll", SetLastError = true)]
private static extern IntPtr SetupDiGetClassDevsW([In] ref Guid ClassGuid,[MarshalAs(UnmanagedType.LPWStr)]string Enumerator,IntPtr parent,UInt32 flags);
[DllImport("setupapi.dll", SetLastError = true)]
private static extern bool SetupDiDestroyDeviceInfoList(IntPtr handle);
[DllImport("setupapi.dll", SetLastError = true)]
private static extern bool SetupDiEnumDeviceInfo(IntPtr deviceInfoSet,UInt32 memberIndex,[Out] out SP_DEVINFO_DATA deviceInfoData);
//used to find device info from device manager
[StructLayout(LayoutKind.Sequential)]
private struct SP_DEVINFO_DATA
{
public UInt32 cbSize;
public Guid classGuid;
public UInt32 devInst;
public IntPtr reserved;
}
private const uint DIGCF_PRESENT = 2;
private const uint ERROR_INVALID_DATA = 13;
private const uint ERROR_NO_MORE_ITEMS = 259;
private const uint ERROR_ELEMENT_NOT_FOUND = 1168;
And here is a simple unit test to prove it works for first device
[Test]
public void TestDevicePresent()
{
var bluetoothClassGuid = "e0cbf06c-cd8b-4647-bb8a-263b43f0f974";
var biometricClassGuid = "53D29EF7-377C-4D14-864B-EB3A85769359";
var cdromdrivClassGiud = "4d36e965-e325-11ce-bfc1-08002be10318";
Assert.False(Native.IsDevicePresent(bluetoothClassGuid));
Assert.False(Native.IsDevicePresent(biometricClassGuid));
Assert.True(Native.IsDevicePresent(cdromdrivClassGiud));
}

Updating images in resource section of an exe (in c#/C)

I have few images embedded in my executable in resource section.
I followed these steps to create my executable:
Generated .resx file for all the images (.jpg) in a directory using some utility. The images are named image1.jpg, image2.jpg and so on.
created .resources file from .resx file using: resgen myResource.resx
Embedded the generated .resource file using /res flag as: csc file.cs /res:myResource.resources
4 I am accessing these images as:
ResourceManager resources = new ResourceManager("myResource", Assembly.GetExecutingAssembly());
Image foo = (System.Drawing.Image)(resources.GetObject("image1"));
This all is working fine as expected. Now I want to change embedded images to some new images. This is what I am currently doing:
class foo
{
[DllImport("kernel32.dll", SetLastError = true)]
static extern IntPtr BeginUpdateResource(string pFileName, bool bDeleteExistingResources);
[DllImport("kernel32.dll", SetLastError = true)]
static extern bool UpdateResource(IntPtr hUpdate, string lpType, string lpName, string wLanguage, Byte[] lpData, uint cbData);
[DllImport("kernel32.dll", SetLastError = true)]
static extern bool EndUpdateResource(IntPtr hUpdate, bool fDiscard);
public static void Main(string[] args)
{
IntPtr handle = BeginUpdateResource(args[0], false);
if (handle.ToInt32() == 0)
throw new Exception("File Not Found: " + fileName + " last err: " + Marshal.GetLastWin32Error());
byte[] imgData = File.ReadAllBytes("SetupImage1.jpg");
int fileSize = imgData.Length;
Console.WriteLine("Updaing resources");
if (UpdateResource(handle, "Image", "image1", "image1", imgData, (uint)fileSize))
{
EndUpdateResource(handle, false);
Console.WriteLine("Update successfully");
}
else
{
Console.WriteLine("Failed to update resource. err: {0}", Marshal.GetLastWin32Error());
}
}
}
The above code is adding a new resource for the specified image (inside IMAGE title with some random number, as seen in Resource hacker), but I want to modify the existing resource data for image1.
I am sure that I am calling UpdateResource with some invalid arguments.
Could any one help pointing that out?
I am using .NET version 2
Thank you,
Vikram
I think you are making a confusion between .NET resources, and Win32 resources. The resources you add embedding with the /res argument to csc.exe are .NET resources that you can successfully read using you ResourceManager snippet code.
Win32 resources are another beast, that is not much "compatible" with the managed .NET world in general, athough you can indeed add them to a .NET exe using the /win32Res argument - note the subtle difference :-)
Now, if you want to modify embedded .NET resources, I don't think there are classes to do it in the framework itself, however you can use the Mono.Cecil library instead. There is an example that demonstrates this here: C# – How to edit resource of an assembly?
And if you want to modify embedded Win32 resources, you code needs some fixes, here is a slightly modified version of it, the most important difference lies in the declaration of UpdateResource:
[DllImport("kernel32.dll", SetLastError = true)]
static extern IntPtr BeginUpdateResource(string pFileName, bool bDeleteExistingResources);
[DllImport("kernel32.dll", SetLastError = true)]
static extern bool UpdateResource(IntPtr hUpdate, string lpType, string lpName, short wLanguage, byte[] lpData, int cbData);
[DllImport("kernel32.dll")]
static extern bool EndUpdateResource(IntPtr hUpdate, bool fDiscard);
public static void Main(string[] args)
{
IntPtr handle = BeginUpdateResource(args[0], false);
if (handle == IntPtr.Zero)
throw new Win32Exception(Marshal.GetLastWin32Error()); // this will automatically throw an error with the appropriate human readable message
try
{
byte[] imgData = File.ReadAllBytes("SetupImage1.jpg");
if (!UpdateResource(handle, "Image", "image1", (short)CultureInfo.CurrentUICulture.LCID, imgData, imgData.Length))
throw new Win32Exception(Marshal.GetLastWin32Error());
}
finally
{
EndUpdateResource(handle, false);
}
}
This is impossible. You cant modify compiled file that you are running.
I believe you can add new images in run time but can't update a resource that is essentially just held in memory.
If you add a resource in run time, it exists but I don't think it is compiled and therefore I don't think it is accessible to you.
Is there a reason you aren't using content instead?

List Hard Links of a file (in C#)

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.

Categories

Resources