I'm trying to get ACL for a shared folder. The code to get security descriptor is following:
private static SECURITY_DESCRIPTOR GetSecurityDescriptor(string path)
{
var sdUtil = new ADsSecurityUtility();
Byte[] temp = (Byte[])sdUtil.GetSecurityDescriptor(path, (int)ADS_PATHTYPE_ENUM.ADS_PATH_FILESHARE, (int)ADS_SD_FORMAT_ENUM.ADS_SD_FORMAT_RAW);
IntPtr ptr = (IntPtr)0;
SECURITY_DESCRIPTOR sd;
try
{
ptr = Marshal.AllocHGlobal(temp.Length);
Marshal.Copy(temp, 0, ptr, temp.Length);
sd = (SECURITY_DESCRIPTOR)Marshal.PtrToStructure(ptr, typeof(SECURITY_DESCRIPTOR));
return sd;
}
catch (Exception)
{
throw new Exception("Couldn't get security descriptor");
}
finally
{
Marshal.FreeHGlobal(ptr);
}
}
SD is ok, I have no problem with that.
Then I'm trying to get DACL and SACL from the SD.
private static List<ACL> GetAcls(SECURITY_DESCRIPTOR sd)
{
List<ACL> result = new List<ACL>(2);
ACL temp = new ACL();
int daclPresent = 0;
int daclDefaulted = 0;
try
{
int res = PInvoke.GetSecurityDescriptorDacl(ref sd, ref daclPresent, ref temp, ref daclDefaulted);
result.Add(temp);
temp = new ACL();
}
catch (Exception) { }
try
{
int res = PInvoke.GetSecurityDescriptorSacl(ref sd, ref daclPresent, ref temp, ref daclDefaulted);
result.Add(temp);
}
catch (Exception) { }
return result;
}
External functions are defined as following:
[DllImport("advapi32.dll")]
public static extern int GetSecurityDescriptorDacl(
[MarshalAs(UnmanagedType.Struct)] ref SECURITY_DESCRIPTOR pSecurityDescriptor,
ref int lpbDaclPresent,
[MarshalAs(UnmanagedType.Struct)] ref ACL pDacl,
ref int lpbDaclDefaulted
);
[DllImport("advapi32.dll")]
public static extern int GetSecurityDescriptorSacl(
[MarshalAs(UnmanagedType.Struct)] ref SECURITY_DESCRIPTOR pSecurityDescriptor,
ref int lpbDaclPresent,
[MarshalAs(UnmanagedType.Struct)] ref ACL pDacl,
ref int lpbDaclDefaulted
);
When I check properties of SD instance I see following:
sd.Dacl
{Permission.ACL}
AceCount: 83886080
AclRevision: 169
AclSize: 1281
Sbz1: 0
Sbz2: 21
sd.Sacl
{Permission.ACL}
AceCount: 6
AclRevision: 20
AclSize: 9961474
Sbz1: 0
Sbz2: 2359297
In total ACL contains 6 ACEs. So it seems SACL contains all of them. However it's not recommended by MS to use these properties. Instead GetSecurityDescriptorDacl and GetSecurityDescriptorSacl should be used. So I use them. And see that count of ACEs in DACL is 0 and count of ACEs in SACL is also 0.
So the question is: how to get properly all ACEs from the security descriptor?
You must treat a SECURITY_DESCRIPTOR as an opaque handle. You can't cast to one as you have done on the line:
sd = (SECURITY_DESCRIPTOR)Marshal.PtrToStructure(ptr,
typeof(SECURITY_DESCRIPTOR));
When you did the above cast you lost all the Owner, Group, DACL and SACL information since you have a self-relative SECURITY_DESCRIPTOR but you are not marshaling the data along with your definition of the structure.
Simply change your declarations of the various API calls (i.e. GetSecurityDescriptorDacl, etc.) to take a byte[] rather than a ref SECURITY_DESCRIPTOR and pass in the byte[] that you received from the ADsSecurityUtility.
Related
I have a WPF application where I use a SaveFileDialog.
The flow is as follow:
1- The user uses the SaveFileDialog to choose a file name and close the dialog
2- The app tries to write to the file.
3- When trying to write to the file, if the file is locked, an IOException is thrown.
4- If I try to open the SaveFileDialog again, the app crashes with "A heap has been corrupted" on ntdll.dll
I can't figure out a solution. Even inside a Try..Catch the app crashes.
Code for the SaveFileDialog
try{
Dispatcher.BeginInvoke(new Action(() =>
{
SaveFileDialog sfd = new SaveFileDialog();
sfd.Reset();
sfd.AddExtension = true;
sfd.CheckFileExists = false;
sfd.CheckPathExists = true;
sfd.CreatePrompt = false;
sfd.OverwritePrompt = true;
sfd.DefaultExt = defaultExt;
sfd.Filter = filter;
sfd.Title = "Save As " + fileTypeDisplay;
sfd.InitialDirectory = specialFolder;
sfd.FileName = newFileNameNoExt;
sfd.FilterIndex = 1;
if (!string.IsNullOrEmpty(specialFolder))
{
FileDialogCustomPlace cp = new FileDialogCustomPlace(specialFolder); // does not throw exceptions
sfd.CustomPlaces.Add(cp);
}
try
{
if (sfd.ShowDialog(MyMainWindow) == true) //<-- ERROR HERE
{
fileToSave = sfd.FileName;
}
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
finally
{
sfd = null;
}
})).Wait();
}
catch(exception ex)
{
...log exception...
}
This is not the answer but how I solved the crash. This is a legacy code from long ago and the clue is that the crash is always after an exception. But why an exception would cause problems to the SaveFileDialog and cause the app to crash?
Going deeper on the code I learned the code below is execute after the file is selected by the user on the SaveFileDialog.
Note the call to the method AnotherUserIsLockingPkg in the catch block.
When I commented out the call to that method, the call to SaveFileDialog.ShowDialog() on the question stopped to crash the application. I'll try to follow other suggestions and see the behavior.
If anybody has any idea of why that happens, comments are appreciated.
FileStream strm = null;
try
{
strm = fi.Open(FileMode.Open, forFileAccessMode, fileShare);
}
catch (IOException) // the file is already open
{
...
fiuEx.IsByOtherUser = AnotherUserIsLockingPkg(filePath);
...
}
catch (Exception ex)
{
...
}
finally
{
....
}
The code below is used to check if the file is being locked by other application. It uses some API calls. Looks like this code was adapted from https://stackoverflow.com/a/20623311/3044154
The method AnotherUserIsLockingPkg is listed down below.
#region Check if another user's process is locking a pkg
[StructLayout(LayoutKind.Sequential)]
private struct RM_UNIQUE_PROCESS
{
public int dwProcessId;
public System.Runtime.InteropServices.ComTypes.FILETIME ProcessStartTime;
}
private const int RmRebootReasonNone = 0;
private const int CCH_RM_MAX_APP_NAME = 255;
private const int CCH_RM_MAX_SVC_NAME = 63;
//private enum RM_APP_TYPE
//{
// RmUnknownApp = 0,
// RmMainWindow = 1,
// RmOtherWindow = 2,
// RmService = 3,
// RmExplorer = 4,
// RmConsole = 5,
// RmCritical = 1000
//}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
private struct RM_PROCESS_INFO
{
public RM_UNIQUE_PROCESS Process;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = CCH_RM_MAX_APP_NAME + 1)]
public string strAppName;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = CCH_RM_MAX_SVC_NAME + 1)]
public string strServiceShortName;
//public RM_APP_TYPE ApplicationType;
public uint AppStatus;
public uint TSSessionId;
[MarshalAs(UnmanagedType.Bool)]
public bool bRestartable;
}
[DllImport("rstrtmgr.dll", CharSet = CharSet.Unicode)]
private static extern int RmRegisterResources(uint pSessionHandle,
UInt32 nFiles,
string[] rgsFilenames,
UInt32 nApplications,
[In] RM_UNIQUE_PROCESS[] rgApplications,
UInt32 nServices,
string[] rgsServiceNames);
[DllImport("rstrtmgr.dll", CharSet = CharSet.Auto)]
private static extern int RmStartSession(out uint pSessionHandle, int dwSessionFlags, string strSessionKey);
[DllImport("rstrtmgr.dll")]
private static extern int RmEndSession(uint pSessionHandle);
[DllImport("rstrtmgr.dll")]
private static extern int RmGetList(uint dwSessionHandle,
out uint pnProcInfoNeeded,
ref uint pnProcInfo,
[In, Out] RM_PROCESS_INFO[] rgAffectedApps,
ref uint lpdwRebootReasons);
/// <summary>
/// Checks if a pkg has been locked by another user
/// </summary>
/// <param name="path">The pkg file path.</param>
/// <param name="includeCurrentUserProcesses">Check also for current user's processes</param>
/// <returns></returns>
public static bool AnotherUserIsLockingPkg(string path, bool includeCurrentUserProcesses = false)
{
uint handle;
string key = Guid.NewGuid().ToString();
Process currentProcess = Process.GetCurrentProcess();
int res = RmStartSession(out handle, 0, key);
if (res != 0)
throw new Exception("Could not begin restart session. Unable to determine file locker.");
try
{
const int ERROR_MORE_DATA = 234;
uint pnProcInfoNeeded = 0,
pnProcInfo = 0,
lpdwRebootReasons = RmRebootReasonNone;
string[] resources = new string[] { path }; // Just checking on one resource.
res = RmRegisterResources(handle, (uint)resources.Length, resources, 0, null, 0, null);
if (res != 0)
throw new Exception("Could not register resource.");
//Note: there's a race condition here -- the first call to RmGetList() returns
// the total number of process. However, when we call RmGetList() again to get
// the actual processes this number may have increased.
res = RmGetList(handle, out pnProcInfoNeeded, ref pnProcInfo, null, ref lpdwRebootReasons);
if (res == ERROR_MORE_DATA)
{
// Create an array to store the process results
RM_PROCESS_INFO[] processInfo = new RM_PROCESS_INFO[pnProcInfoNeeded];
pnProcInfo = pnProcInfoNeeded;
// Get the list
res = RmGetList(handle, out pnProcInfoNeeded, ref pnProcInfo, processInfo, ref lpdwRebootReasons);
//pnProcInfo contains all the processes that are using the pkg
if (res == 0)
{
// Enumerate all of the results and check for waf3 process and not same session
for (int i = 0; i < pnProcInfo; i++)
{
try
{
if (includeCurrentUserProcesses)
{
if (processInfo[i].strAppName == currentProcess.ProcessName)
return true;
}
else
{
if (processInfo[i].strAppName == currentProcess.MainModule.ModuleName && processInfo[i].TSSessionId != currentProcess.SessionId)
return true;
}
}
// catch the error -- in case the process is no longer running
catch (ArgumentException)
{ }
}
}
else
throw new Exception("Could not list processes locking resource.");
}
else if (res != 0)
throw new Exception("Could not list processes locking resource. Failed to get size of result.");
}
finally
{
RmEndSession(handle);
}
return false;
}
#endregion
We suddenly have problems with the smart card api on some windows installations.
There seem to be a memory leak while calling the SCardEstablishContext function.
The problem can be reproduced in a console application with the code sample available at
http://www.pinvoke.net/default.aspx/winscard.scardestablishcontext
class Program
{
#region Win32
// WinSCard APIs to be imported.
[DllImport("WinScard.dll")]
static extern int SCardEstablishContext(uint dwScope,
IntPtr notUsed1,
IntPtr notUsed2,
out IntPtr phContext);
[DllImport("WinScard.dll")]
static extern int SCardReleaseContext(IntPtr phContext);
[DllImport("WinScard.dll")]
static extern int SCardConnect(IntPtr hContext,
string cReaderName,
uint dwShareMode,
uint dwPrefProtocol,
ref IntPtr phCard,
ref IntPtr ActiveProtocol);
[DllImport("WinScard.dll")]
static extern int SCardDisconnect(IntPtr hCard, int Disposition);
[DllImport("WinScard.dll", EntryPoint = "SCardListReadersA", CharSet = CharSet.Ansi)]
static extern int SCardListReaders(
IntPtr hContext,
byte[] mszGroups,
byte[] mszReaders,
ref UInt32 pcchReaders);
#endregion
static void Main(string[] args)
{
while (true)
{
SmartCardInserted();
System.Threading.Thread.Sleep(10);
}
}
internal static bool SmartCardInserted()
{
bool cardInserted = false;
IntPtr hContext = IntPtr.Zero;
try
{
List<string> readersList = new List<string>();
int ret = 0;
uint pcchReaders = 0;
int nullindex = -1;
char nullchar = (char)0;
// Establish context.
ret = SCardEstablishContext(2, IntPtr.Zero, IntPtr.Zero, out hContext);
// First call with 3rd parameter set to null gets readers buffer length.
ret = SCardListReaders(hContext, null, null, ref pcchReaders);
byte[] mszReaders = new byte[pcchReaders];
// Fill readers buffer with second call.
ret = SCardListReaders(hContext, null, mszReaders, ref pcchReaders);
// Populate List with readers.
ASCIIEncoding ascii = new ASCIIEncoding();
string currbuff = ascii.GetString(mszReaders);
int len = (int)pcchReaders;
if (len > 0)
{
while (currbuff[0] != nullchar)
{
nullindex = currbuff.IndexOf(nullchar); // Get null end character.
string reader = currbuff.Substring(0, nullindex);
readersList.Add(reader);
len = len - (reader.Length + 1);
currbuff = currbuff.Substring(nullindex + 1, len);
}
}
// We have list of readers, check for cards.
IntPtr phCard = IntPtr.Zero;
IntPtr ActiveProtocol = IntPtr.Zero;
int result = 0;
foreach (string readerName in readersList)
{
try
{
result = SCardConnect(hContext, readerName, 2, 3, ref phCard, ref ActiveProtocol);
if (result == 0)
{
cardInserted = true;
break;
}
}
finally
{
SCardDisconnect(phCard, 0);
}
}
}
finally
{
SCardReleaseContext(hContext);
}
return cardInserted;
}
}
To test, we call the method SmartCardInserted() in an infinite loop with a small delay => the memory grows constantly and new hadles are allocated.
We see this problem on systems runing Windows 10 or Windows Server 2012, but not on Windows Server 2008.
Any ideas are greatly appreciated!
The problem seems to have been released with v1709 of Windows 10. The shortest amount of code to reproduce the bug is
while(true) {
ret = SCardEstablishContext(2, IntPtr.Zero, IntPtr.Zero, out hContext);
SCardReleaseContext(hContext);
}
It leaks ~264 bytes of memory each time a context is established and released.
If you maintain hContext outside of the loop and only create a context if it's IntPtr.Zero you should be able to avoid the leak. Then when you call SCardListReaders, check to see if you get SCARD_E_INVALID_HANDLE back and invalidate your hContext.
class Program
{
#region Win32
// WinSCard APIs to be imported.
[DllImport("WinScard.dll")]
static extern int SCardEstablishContext(uint dwScope,
IntPtr notUsed1,
IntPtr notUsed2,
out IntPtr phContext);
[DllImport("WinScard.dll")]
static extern int SCardReleaseContext(IntPtr phContext);
[DllImport("WinScard.dll")]
static extern int SCardConnect(IntPtr hContext,
string cReaderName,
uint dwShareMode,
uint dwPrefProtocol,
ref IntPtr phCard,
ref IntPtr ActiveProtocol);
[DllImport("WinScard.dll")]
static extern int SCardDisconnect(IntPtr hCard, int Disposition);
[DllImport("WinScard.dll", EntryPoint = "SCardListReadersA", CharSet = CharSet.Ansi)]
static extern int SCardListReaders(
IntPtr hContext,
byte[] mszGroups,
byte[] mszReaders,
ref UInt32 pcchReaders);
#endregion
static void Main(string[] args)
{
IntPtr hContext = IntPtr.Zero;
while (true)
{
SmartCardInserted(hContext);
System.Threading.Thread.Sleep(10);
}
SCardReleaseContext(hContext);
}
internal static bool SmartCardInserted(IntPtr hContext)
{
bool cardInserted = false;
try
{
List<string> readersList = new List<string>();
int ret = 0;
uint pcchReaders = 0;
int nullindex = -1;
char nullchar = (char)0;
// Establish context.
if(hContext == IntPtr.Zero)
ret = SCardEstablishContext(2, IntPtr.Zero, IntPtr.Zero, out hContext);
// First call with 3rd parameter set to null gets readers buffer length.
ret = SCardListReaders(hContext, null, null, ref pcchReaders);
if(ret == 0x80100003) // SCARD_E_INVALID_HANDLE = 0x80100003, // The supplied handle was invalid
{
try
{
SCardReleaseContext(hContext);
}
catch {}
finally
{
hContext = IntPtr.Zero;
}
return false;
}
byte[] mszReaders = new byte[pcchReaders];
// Fill readers buffer with second call.
ret = SCardListReaders(hContext, null, mszReaders, ref pcchReaders);
// Populate List with readers.
ASCIIEncoding ascii = new ASCIIEncoding();
string currbuff = ascii.GetString(mszReaders);
int len = (int)pcchReaders;
if (len > 0)
{
while (currbuff[0] != nullchar)
{
nullindex = currbuff.IndexOf(nullchar); // Get null end character.
string reader = currbuff.Substring(0, nullindex);
readersList.Add(reader);
len = len - (reader.Length + 1);
currbuff = currbuff.Substring(nullindex + 1, len);
}
}
// We have list of readers, check for cards.
IntPtr phCard = IntPtr.Zero;
IntPtr ActiveProtocol = IntPtr.Zero;
int result = 0;
foreach (string readerName in readersList)
{
try
{
result = SCardConnect(hContext, readerName, 2, 3, ref phCard, ref ActiveProtocol);
if (result == 0)
{
cardInserted = true;
break;
}
}
finally
{
SCardDisconnect(phCard, 0);
}
}
}
return cardInserted;
}
}
It's a workaround until the Winscard.dll API is fixed.
I have the following code to import the IExtractImage interface.
[ComImport]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
[Guid("BB2E617C-0920-11d1-9A0B-00C04FC2D6C1")]
public interface IExtractImage
{
[PreserveSig]
Int32 GetLocation([MarshalAs(UnmanagedType.LPWStr)] out StringBuilder pszPathBuffer,
int cch,
ref int pdwPriority,
SIZE prgSize,
int dwRecClrDepth,
ref int pdwFlags);
[PreserveSig]
Int32 Extract(out IntPtr phBmpThumbnail);
}
I also have the IShellFolder imported, which I am not mentioning here for brevity. My intention is to access the thumbnail of a file using the shellfolder. So here's my code to retrieve the thumbnail. But my call to IExtractImage.GetLocation() is failing with a NullReference exception, stating
"An exception of type 'System.NullReferenceException' occurred in CCDash.exe but was not handled in user code
Additional information: Object reference not set to an instance of an object."
Can someone please help me identify what is it that I am missing?
public Bitmap GetThumbNail(string mediaFileName)
{
IShellFolder shellDesktop;
SHGetDesktopFolder(out shellDesktop);
IntPtr pidlRoot;
uint attribute = 0;
string mediaPath = "C:\\Users\\<user>\\Videos"; // I have hard-coded the path for now
uint pchEaten = 0;
// Get the pidl of the media folder
shellDesktop.ParseDisplayName(IntPtr.Zero, IntPtr.Zero, mediaPath, ref pchEaten, out pidlRoot, ref attribute);
Guid mediaFolderGuid = new Guid("000214E6-0000-0000-C000-000000000046");
shellDesktop.BindToObject(pidlRoot, IntPtr.Zero, mediaFolderGuid, out shellMediaFolder);
IntPtr pidlMediaFolder;
// Get the pidl of the media file
shellMediaFolder.ParseDisplayName(IntPtr.Zero, IntPtr.Zero, mediaFileName, ref pchEaten, out pidlMediaFolder, ref attribute);
Guid mediaFileImgGuid = new Guid("BB2E617C-0920-11d1-9A0B-00C04FC2D6C1");
uint rfgRes = 0;
IExtractImage extractImage;
shellMediaFolder.GetUIObjectOf(IntPtr.Zero, 1, out pidlMediaFolder, mediaFileImgGuid, ref rfgRes, out extractImage);
SIZE size = new SIZE
{
cx = 40,
cy = 40
};
int flags = 0x40 | 0x40;
StringBuilder location = new StringBuilder(260, 260);
int priority = 0;
int requestedColourDepth = 0x20;
IntPtr hBmp = IntPtr.Zero;
// Now get the image
extractImage.GetLocation(out location, location.Capacity, ref priority, size, requestedColourDepth, ref flags);
extractImage.Extract(out hBmp);
Bitmap thumbnail = Image.FromHbitmap(hBmp);
return thumbnail;
}
EDIT:
I have now modified my code as below. Not very different from the first version, but with a little more error handling and better documentation and variable names that can help us understand my code better. Here's the modified code:
public Bitmap GetThumbNail(string mediaFileName)
{
Bitmap thumbnail = null;
//Step 1: Use SHGetDesktopFolder to get the desktop folder.
IShellFolder shellDesktop;
SHGetDesktopFolder(out shellDesktop);
if (shellDesktop != null)
{
IntPtr pidlMediaFolder;
try
{
uint attribute = 0;
string mediaPath = Path.GetDirectoryName(mediaFileName);
uint pchEaten = 0;
// Step 2: Using the desktop's IShellFolder, pass the file's parent folder path name into ParseDisplayName to get its PIDL.
shellDesktop.ParseDisplayName(IntPtr.Zero, IntPtr.Zero, mediaPath, ref pchEaten, out pidlMediaFolder, ref attribute);
}
catch (Exception)
{
Marshal.ReleaseComObject(shellDesktop);
return null;
}
if (pidlMediaFolder != IntPtr.Zero)
{
Guid mediaFolderGuid = new Guid("000214E6-0000-0000-C000-000000000046");
IShellFolder shellMediaFolder;
// Step 3: Using the desktop's IShellFolder, pass the PIDL into the BindToObject method
// and get the IShellFolder interface of the file's parent folder.
try
{
shellDesktop.BindToObject(pidlMediaFolder, IntPtr.Zero, mediaFolderGuid, out shellMediaFolder);
}
catch (Exception)
{
Marshal.ReleaseComObject(shellDesktop);
return null;
}
if (shellMediaFolder != null)
{
IntPtr pidlMediaFile;
uint attribute = 0;
uint pchEaten = 0;
// Step 4: Using the parent folder's IShellFolder, pass the file name into ParseDisplayName to get its PIDL.
int ret = shellMediaFolder.ParseDisplayName(IntPtr.Zero, IntPtr.Zero, mediaFileName, ref pchEaten, out pidlMediaFile, ref attribute);
Guid mediaFileImgGuid = new Guid("BB2E617C-0920-11d1-9A0B-00C04FC2D6C1");
uint rfgRes = 0;
IExtractImage extractImage;
// Step 5: Using the parent folder's IShellFolder, pass the file's PIDL
// into the GetUIObjectOf. method to get the IExtractImage interface.
ret = shellMediaFolder.GetUIObjectOf(IntPtr.Zero, 1, out pidlMediaFile, mediaFileImgGuid, ref rfgRes, out extractImage);
SIZE size = new SIZE
{
cx = 40,
cy = 40
};
uint flags = 0x0200;
StringBuilder location = new StringBuilder(260, 260);
int priority = 0;
int requestedColourDepth = 0x20;
IntPtr hBmp = IntPtr.Zero;
// Now get the image
extractImage.GetLocation(out location, location.Capacity, ref priority, size, requestedColourDepth, ref flags);
extractImage.Extract(out hBmp);
thumbnail = Image.FromHbitmap(hBmp);
}
}
}
return thumbnail;
}
I see that at step 4, the pidlMediaFile is not retrieved correctly and it's value is still 0 after the ParseDisplayName() call. This is where the problem begins. I am not sure why the pidl for the filename is not retrieved whereas it is retrieved successfully for the file's parent folder.
It looks like extractImage is being returned null from GetUIObjectOf(). Try checking the HRESULT return value from GetUIObjectOf() to find out why a null value is being returned.
EDIT:
From http://msdn.microsoft.com/en-us/library/windows/desktop/aa378137%28v=vs.85%29.aspx:
E_INVALIDARG One or more arguments are not valid 0x80070057
According to the documentation, the third argument is an [in] argument, not an [out] argument.
I have a situation where I want to perform special processing on some Windows shell special folders (those corresponding to values in the CSIDL enum.) (My solution must be WinXP compatible.) The problem I'm having is that when I encounter IShellFolders as I work my way down the heirarchy, I cannot find a way to match up the IShellFolders to their CSIDL.
This is my current approach:
Initialize a static one-to-one data structure (csidlToFromFullPIdl) of all CSIDLs to their pIDLs returned by SHGetSpecialFolderLocation.
foreach (CSIDL csidl in Enum.GetValues(typeof(CSIDL))
{
IntPtr fullPIdl = IntPtr.Zero;
int hResult = ShellApi.SHGetSpecialFolderLocation(IntPtr.Zero, csidl, ref fullPIdl);
if (hResult != 0)
Marshal.ThrowExceptionForHR(hResult);
csidlToFromFullPIdl.Add(csidl, fullPIdl);
}
Start the heirarchy with the Desktop IShellFolder:
int hResult = ShellApi.SHGetDesktopFolder(ref _shellFolder);
hResult = ShellApi.SHGetSpecialFolderLocation(IntPtr.Zero, CSIDL.CSIDL_DESKTOP, ref _fullPIdl);
Retrieve children like so:
hResult = _shellFolder.EnumObjects(IntPtr.Zero, SHCONTF.SHCONTF_FOLDERS, out pEnum);
// Then repeatedly call:
pEnum.Next(1, out childRelativePIdl, out numberGotten);
Construct new fully-qualified pIDLs for the children like so:
_fullPIdl = ShellApi.ILCombine(parentFullPIdl, childRelativePIdl);
(Finally, retrieve the IShellFolder for the child using:)
hResultUint = parentShellItem.ShellFolder.BindToObject(childRelativePIdl, IntPtr.Zero, ShellApi.IID_IShellFolder, out _shellFolder);
The problem I'm having is that neither the childRelativePIdl nor the _fullPIdl correspond to any pIDLs in csidlToFromFullPIdl.
TIA.
FYI on Vista machines the GUID corresponding to KNOWNFOLDERIDs may be a solution (but not for me as I must be WinXP compatible.)
I should also say that I think using the paths of the special folders (via SHGetSpecialFolderPath) is insufficient because several of the special folders in which I'm interested do not have paths. (E.g., CSIDL_DRIVES and CSIDL_NETWORK.)
I'm trying two approaches to this. The first is to use SHGetDataFromIDList to retrieve the Clsid, which I can then compare to known Clsids:
public static Guid GetClsidFromFullPIdl(IntPtr fullPIdl)
{
// Get both parent's IShellFolder and pIDL relative to parent from full pIDL
IntPtr pParentShellFolder;
IntPtr relativePIdl = IntPtr.Zero;
int hResultInt = ShellApi.SHBindToParent(fullPIdl, ShellGuids.IID_IShellFolder, out pParentShellFolder, ref relativePIdl);
if (hResultInt != (int)HRESULT.S_OK)
Marshal.ThrowExceptionForHR(hResultInt);
object parentShellFolderObj = System.Runtime.InteropServices.Marshal.GetTypedObjectForIUnknown(
pParentShellFolder, typeof(IShellFolder));
IShellFolder parentShellFolder = (IShellFolder)parentShellFolderObj;
SHDESCRIPTIONID descriptionId = new SHDESCRIPTIONID();
IntPtr pDescriptionId = MarshalToPointer(descriptionId);
// Next line returns hResult corresponding to NotImplementedException
hResultInt = ShellApi.SHGetDataFromIDList(parentShellFolder, ref relativePIdl, SHGDFIL.SHGDFIL_DESCRIPTIONID, pDescriptionId,
Marshal.SizeOf(typeof(SHDESCRIPTIONID)));
if (hResultInt != (int)HRESULT.S_OK)
Marshal.ThrowExceptionForHR(hResultInt);
if (parentShellFolder != null)
Marshal.ReleaseComObject(parentShellFolder);
return descriptionId.Clsid;
}
static IntPtr MarshalToPointer(object data)
{
IntPtr pData = Marshal.AllocHGlobal(Marshal.SizeOf(data));
Marshal.StructureToPtr(data, pData, false);
return pData;
}
The problem with this approach is that the call to SHGetDataFromIDList returns an hResult that corresponds to throwing a NotImplementedException. Does this mean that SHGetDataFromIDList is unavailable on my system? (WinXP SP3.)
My second approach is to compare the item identifier lists referenced by two pointers to item identifier lists and see if they are equal. I'm implementing a technique coded here in C. This is what I have so far:
Per Raymond Chen: ITEMIDLISTs can be equivalent without being byte-for-byte identical. Use IShellFolder::CompareIDs to test equivalence.
static bool pIdlsAreEquivalent(IntPtr pIdl1, IntPtr pIdl2)
{
if (pIdl1 == pIdl2) return true;
if (pIdl1 == IntPtr.Zero || pIdl2 == IntPtr.Zero) return false;
int pIdl1Size = GetItemIDSize(pIdl1);
if (pIdl1Size != GetItemIDSize(pIdl2)) return false;
byte[] byteArray1 = new byte[pIdl1Size];
byte[] byteArray2 = new byte[pIdl1Size];
Marshal.Copy(pIdl1, byteArray1, 0, pIdl1Size);
Marshal.Copy(pIdl2, byteArray2, 0, pIdl1Size);
for (int i = 0; i < pIdl1Size; i++)
{
if (byteArray1[i] != byteArray2[i])
return false;
}
return true;
}
static int GetItemIdSize(IntPtr pIdl)
{
if (pIdl == IntPtr.Zero) return 0;
int size = 0;
// Next line throws AccessViolationException
ITEMIDLIST idl = (ITEMIDLIST)Marshal.PtrToStructure(pIdl, typeof(ITEMIDLIST));
while (idl.mkid.cb > 0)
{
size += idl.mkid.cb;
pIdl = (IntPtr)((int)pIdl + idl.mkid.cb);
idl = (ITEMIDLIST)Marshal.PtrToStructure(pIdl, typeof(ITEMIDLIST));
}
return size;
}
public struct ITEMIDLIST
{
public SHITEMID mkid;
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
public struct SHITEMID
{
public ushort cb; // The size of identifier, in bytes, including cb itself.
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 1)]
public byte[] abID; // A variable-length item identifier.
}
I've successfully read a PE header from an unmanaged module loaded into memory by another process. What I'd like to do now is read the names of this module's exports. Basically, this is what I have so far (I've left out a majority of the PE parsing code, because I already know it works):
Extensions
public static IntPtr Increment(this IntPtr ptr, int amount)
{
return new IntPtr(ptr.ToInt64() + amount);
}
public static T ToStruct<T>(this byte[] data)
{
GCHandle handle = GCHandle.Alloc(data, GCHandleType.Pinned);
T result = (T)Marshal.PtrToStructure(handle.AddrOfPinnedObject(), typeof(T));
handle.Free();
return result;
}
public static byte[] ReadBytes(this Process process, IntPtr baseAddress, int size)
{
int bytesRead;
byte[] bytes = new byte[size];
Native.ReadProcessMemory(process.Handle, baseAddress, bytes, size, out bytesRead);
return bytes;
}
public static T ReadStruct<T>(this Process process, IntPtr baseAddress)
{
byte[] bytes = ReadBytes(process, baseAddress, Marshal.SizeOf(typeof(T)));
return bytes.ToStruct<T>();
}
public static string ReadString(this Process process, IntPtr baseAddress, int size)
{
byte[] bytes = ReadBytes(process, baseAddress, size);
return Encoding.ASCII.GetString(bytes);
}
GetExports()
Native.IMAGE_DATA_DIRECTORY dataDirectory =
NtHeaders.OptionalHeader.DataDirectory[Native.IMAGE_DIRECTORY_ENTRY_EXPORT];
if (dataDirectory.VirtualAddress > 0 && dataDirectory.Size > 0)
{
Native.IMAGE_EXPORT_DIRECTORY exportDirectory =
_process.ReadStruct<Native.IMAGE_EXPORT_DIRECTORY>(
_baseAddress.Increment((int)dataDirectory.VirtualAddress));
IntPtr namesAddress = _baseAddress.Increment((int)exportDirectory.AddressOfNames);
IntPtr nameOrdinalsAddress = _baseAddress.Increment((int)exportDirectory.AddressOfNameOrdinals);
IntPtr functionsAddress = _baseAddress.Increment((int)exportDirectory.AddressOfFunctions);
for (int i = 0; i < exportDirectory.NumberOfFunctions; i++)
{
Console.WriteLine(_process.ReadString(namesAddress.Increment(i * 4), 64));
}
}
When I run this, all I get is a pattern of double question marks, then completely random characters. I know the header is being read correctly, because the signatures are correct. The problem has to lie in the way that I'm iterating over the function list.
The code at this link seems to suggest that the names and ordinals form a matched pair of arrays, counted up to NumberOfNames, and that the functions are separate. So your loop may be iterating the wrong number of times, but that doesn't explain why you're seeing bad strings from the very beginning.
For just printing names, I'm having success with a loop like the one shown below. I think the call to ImageRvaToVa may be what you need to get the correct strings? However I don't know whether that function will work unless you've actually loaded the image by calling MapAndLoad -- that's what the documentation requests, and the mapping did not seem to work in some quick experiments I did using LoadLibrary instead.
Here's the pInvoke declaration:
[DllImport("DbgHelp.dll", CallingConvention = CallingConvention.StdCall), SuppressUnmanagedCodeSecurity]
public static extern IntPtr ImageRvaToVa(
IntPtr NtHeaders,
IntPtr Base,
uint Rva,
IntPtr LastRvaSection);
and here's my main loop:
LOADED_IMAGE loadedImage = ...; // populated with MapAndLoad
IMAGE_EXPORT_DIRECTORY* pIID = ...; // populated with ImageDirectoryEntryToData
uint* pFuncNames = (uint*)
ImageRvaToVa(
loadedImage.FileHeader,
loadedImage.MappedAddress,
pIID->AddressOfNames,
IntPtr.Zero);
for (uint i = 0; i < pIID->NumberOfNames; i++ )
{
uint funcNameRVA = pFuncNames[i];
if (funcNameRVA != 0)
{
char* funcName =
(char*) (ImageRvaToVa(loadedImage.FileHeader,
loadedImage.MappedAddress,
funcNameRVA,
IntPtr.Zero));
var name = Marshal.PtrToStringAnsi((IntPtr) funcName);
Console.WriteLine(" funcName: {0}", name);
}
}