I tried to show only the drives shared with Remote Desktop in a file selection dialog. Below is what I want to show:
(Image) Windows 10 - Only the shared drives are shown.
But the list of the drives and folders in the left pane are different according to the Windows OS version. The links to the screenshots are in a reply comment.
How can we show the same list of the drives and folders in the multiple Windows versions?
This is the implementation:
CFileDialog my_file_dialog(TRUE);
CComPtr<IFileDialogCustomize> file_dialog = my_file_dialog.GetIFileDialogCustomize();
CComPtr<IFileDialog2> dialog2;
if (FAILED(file_dialog->QueryInterface(&dialog2))) {
::AfxMessageBox(_T("Failed to query the interface of IFileDialog2."));
return;
}
CComPtr<IShellItemFilter> shell_item_filter =
new CVisibleShellItemFilter(REMOTE_APP_VISIBLE_PATHS);
if (FAILED(dialog2->SetFilter(shell_item_filter))) {
::AfxMessageBox(_T("Failed to set the shell item filter to the file dialog."));
return;
}
CComPtr<IShellItem> shell_item;
if (FAILED(::SHCreateItemInKnownFolder(FOLDERID_ComputerFolder, 0, nullptr, IID_PPV_ARGS(&shell_item)))) {
::AfxMessageBox(_T("Failed to create a shell item for the computer folder."));
return;
}
if (FAILED(dialog2->SetDefaultFolder(shell_item))) {
::AfxMessageBox(_T("Failed to set the default folder to the computer folder."));
return;
}
CComPtr<IShellItem> shell_item_desktop;
if (FAILED(::SHCreateItemInKnownFolder(FOLDERID_Desktop, 0, nullptr, IID_PPV_ARGS(&shell_item_desktop)))) {
::AfxMessageBox(_T("Failed to create a shell item for the desktop."));
return;
}
if (FAILED(dialog2->SetNavigationRoot(shell_item_desktop))) {
::AfxMessageBox(_T("Failed to set the navigation root to the computer folder."));
return;
}
CComPtr<IShellItem> currently_selected_folder;
if (FAILED(dialog2->GetFolder(¤tly_selected_folder))) {
::AfxMessageBox(_T("Failed to set the navigation root to the computer folder."));
return;
}
if (!IsVisible(ToPathPartsList(REMOTE_APP_VISIBLE_PATHS), currently_selected_folder)) {
if (FAILED(dialog2->SetFolder(shell_item))) {
::AfxMessageBox(_T("Failed to set the folder to the computer folder."));
return;
}
}
if (my_file_dialog.DoModal() == IDOK) {
m_message.SetWindowTextW(my_file_dialog.GetPathName());
}
else {
m_message.SetWindowTextW(_T(""));
}
What I did were:
Construct an instance of CFiledialog.
Retrieved an instance of IFileDialogCustomize by CFiledialog::GetIFileDialogCustomize().
Retrieved an instance of IFileDialog2 from the isntance of IFileDialogCustomize.
Set an instance of IShellItemFilter to the file dialog by IFileDialog::SetFilter().
Set the default folder to the PC (My Computer) folder by IFileDialog::SetDefaultFolder().
Set the navigation root to the PC (My Computer) folder by IFileDialog::SetNavigationRoot().
Set the folder to the PC (My Computer) folder by IFileDialog::SetFolder() if the current folder is not a shared drive or its descendant folders.
Show the file dialog.
I did 2. to retrieve an instance of IFileDialog2 from the instance of IFileDialogCustomize. This is because I want to support both "Open" and "Save" file dialog by the same routine.
The point is 4.. I will show the implementation of IShellItemFilter later.
I did 5. because a folder other than a shared drive or its descendant folders is shown if the default folder is not than a shared drive or its descendant folders.
I did 6. because I don't want to show the minimum contents in the file dialog, and I don't want to show the desktop folder.
I did 7. because a folder other than a shared drive or its descendant folders is shown if the current folder is not than a shared drive or its descendant folders.
The implementation of IShellItemFilter was:
// If the desktop_absolute_parsing is "::{20D04FE0-3AEA-1069-A2D8-08002B30309D}", it shows the
// virtual folder that represents My Computer (PC).
// http://www.atmarkit.co.jp/ait/articles/1004/09/news094.html
static const std::wstring MY_COMPUTER_PATH = L"::{20D04FE0-3AEA-1069-A2D8-08002B30309D}";
// If the desktop_absolute_parsing starts with \\tsclient\, it shows a drive shared with
// Remote Desktop (or RemoteApp) or its child folder.
// https://social.msdn.microsoft.com/Forums/vstudio/ja-JP/74673059-17b0-4a80-80ac-66b5dc419b56?forum=vcgeneralja
static const std::wstring REMOTE_APP_SHARED_FOLDER_PATH_PREFIX = L"\\\\tsclient\\";
static const std::vector<std::wstring> REMOTE_APP_VISIBLE_PATHS = {
// Add My Compter to the paths so that users can navigate from the My Compter in the folder
// view.
MY_COMPUTER_PATH,
REMOTE_APP_SHARED_FOLDER_PATH_PREFIX,
};
// Converts a give string to lower cases.
// str String
// return Lower case string.
std::wstring ToLower(std::wstring str) {
std::transform(str.begin(), str.end(), str.begin(),
[](auto ch) { return std::tolower(ch, std::locale()); });
return str;
}
// Split a givn path to the parts. Users need to split a path into the path parts, and pass to
// the function below for the processing speed.
// ex) "c:\\windows\\system32" -> {"c:", "\\", "windows", "system32"}
// file_path Path to be splitted.
// return Path parts.
std::vector<std::wstring> SplitPath(const std::wstring& file_path) {
using std::experimental::filesystem::v1::path;
path p(file_path);
return std::vector<std::wstring>(p.begin(), p.end());
}
// Checks if a path is equal to or an ancestor of another path.
// ancestor_path_parts Given path parts.
// descendant_path_parts Other path parts.
// returns true if ancestor_path_parts is equal to or an ancestor of descendant_path_parts.
// false, otherwise.
bool IsPathSelfOrAncestor(const std::vector<std::wstring>& ancestor_path_parts,
const std::vector<std::wstring>& descendant_path_parts) {
// Do not compare the path strings directly. Because "C:\Windows\System32" matches
// "C:\Windows\System", for example.
return std::search(descendant_path_parts.begin(), descendant_path_parts.end(),
ancestor_path_parts.begin(), ancestor_path_parts.end()) ==
descendant_path_parts.begin();
}
// Checks if two given paths are in a direct line. i.e. A path is equal to, ancestor of, or
// decendant of another path.
// path_parts1 Given path parts.
// path_parts2 Other path parts.
// return true if the given two paths are in a direct line. false, otherwise.
bool IsInDirectLine(const std::vector<std::wstring>& path_parts1, const std::vector<std::wstring>& path_parts2) {
return IsPathSelfOrAncestor(path_parts1, path_parts2) ||
IsPathSelfOrAncestor(path_parts2, path_parts1);
}
// Gets the display name from a given shell item.
// sigdnName SIGDN name.
// shell_item Shell item.
// name Display name.
// return S_OK if this method succeeds. false, otherwise.
HRESULT GetDisplayName(IShellItem* shell_item, SIGDN sigdnName, std::wstring& name) {
LPWSTR raw_name = nullptr;
HRESULT result;
if (FAILED(result = shell_item->GetDisplayName(sigdnName, &raw_name))) {
return result;
}
name = raw_name;
::CoTaskMemFree(raw_name);
raw_name = nullptr;
return S_OK;
}
// Checks if a given shell item is visible in a file/folder view.
// visible_path_parts_list List of the visble paths parts.
// shell_item Shell item to be checked.
// return true if the given shell item is visible. false, otherwise.
bool IsVisible(const std::vector<std::vector<std::wstring> >& visible_path_parts_list, IShellItem* shell_item) {
std::wstring desktop_absolute_parsing;
if (FAILED(GetDisplayName(shell_item, SIGDN_DESKTOPABSOLUTEPARSING,
desktop_absolute_parsing))) {
::AfxMessageBox(_T("Failed to get the diplay name of a shell item."));
return false;
}
auto path_parts = SplitPath(ToLower(desktop_absolute_parsing));
for (const auto& visible_path_parts : visible_path_parts_list) {
// Check if shell_item and visible_path are in the direct line. i.e. shell_item and
// visible_path are same, shell_item is an ancestor of visible_path, or shell_item is
// an descendant of visible_path.
// Let users to navigate in the case that shell_item is an ancestor of visible_path.
// Otherwise, users can not navigate to the visible_path through the folder view in
// the file selection dialog.
if (IsInDirectLine(path_parts, visible_path_parts)) {
return true;
}
}
return false;
}
// Converts the list of paths into the list of the path parts.
std::vector<std::vector<std::wstring> > ToPathPartsList(
const std::vector<std::wstring>& paths) {
std::vector<std::vector<std::wstring> > path_parts_list;
for (const auto& path : paths) {
path_parts_list.push_back(SplitPath(ToLower(path)));
}
return path_parts_list;
}
// CVisibleShellItemFilter show only the visible shell items which are listed in the given list
// of paths.
class CVisibleShellItemFilter : public IShellItemFilter {
public:
CVisibleShellItemFilter(const std::vector<std::wstring>& visible_paths) :
m_visible_path_parts_list(ToPathPartsList(visible_paths)) { }
HRESULT QueryInterface(REFIID riid, void** ppvObject) override {
if (ppvObject == nullptr) {
return E_POINTER;
}
if (riid == IID_IUnknown || riid == IID_IShellItemFilter) {
*ppvObject = static_cast<void*>(this);
AddRef();
return NO_ERROR;
}
else {
*ppvObject = nullptr;
return E_NOINTERFACE;
}
}
ULONG AddRef() override {
return ++m_reference_count;
}
ULONG Release() override {
ULONG reference_count = --m_reference_count;
if (reference_count == 0) {
delete this;
}
return reference_count;
}
HRESULT IncludeItem(IShellItem *psi) override {
return IsVisible(m_visible_path_parts_list, psi) ? S_OK : S_FALSE;
}
HRESULT GetEnumFlagsForItem(IShellItem *psi, SHCONTF *pgrfFlags) override {
*pgrfFlags = static_cast<SHCONTF>(-1);
return S_OK;
}
private:
ULONG m_reference_count = 0;
const std::vector<std::vector<std::wstring> > m_visible_path_parts_list;
};
CVisibleShellItemFilter::IncludeItem() returns S_OK if the desktop absolute parsing of a file is "::{20D04FE0-3AEA-1069-A2D8-08002B30309D}" or starts with "\tsclient\". "::{20D04FE0-3AEA-1069-A2D8-08002B30309D}" means PC (My Computer) folder, and "\tsclient\" is the file path prefix of the shared drives.
Thanks,
Updated (2017/09/07 11:54 JST)
"the content" -> "the list of the drives and folders"
Related
I'm using dropzone.js to be able to easily upload files and folders. I do not get an notification for empty folders, is there any way to have an event fired for empty folders?
Thank you.
So this is what I came up with, not the most elegant solution, but does what I need to be able to handle emptyfolders. Would be great if dropzone.js had an option for enabling emptyfolders to be returned.
I made the following changes to the dropzone.js
At the very top I added
var passedFolders = [];
// used to store the names of fodlers that were passed, with either files or without
// the key is the path name, the value is true if empty, false if it had contents
Then under the section
var readEntries = function readEntries() {
return dirReader.readEntries(function (entries) {
change it to
var readEntries = function readEntries() {
return dirReader.readEntries(function (entries) {
if (entries.length > 0) {
var _iterator7 = dropzone_createForOfIteratorHelper(entries, true),
_step7;
try {
for (_iterator7.s(); !(_step7 = _iterator7.n()).done;) {
var entry = _step7.value;
if (entry.isFile) {
//There is a a file in the folder, so we can set it's value to false
passedFolders[entry.fullPath.substring(0, entry.fullPath.lastIndexOf(entry.name)-1)] = false //-1 to remove the last slash
entry.file(function (file) {
if (_this5.options.ignoreHiddenFiles && file.name.substring(0, 1) === ".") {
return;
}
file.fullPath = "".concat(path, "/").concat(file.name);
return _this5.addFile(file);
});
} else if (entry.isDirectory) {
//This is just a folder, not a file. Store the folder path information set to true, it will remain true if the folder remains empty
passedFolders[entry.fullPath] = true;
_this5._addFilesFromDirectory(entry, "".concat(path, "/").concat(entry.name));
}
} // Recursively call readEntries() again, since browser only handle
// the first 100 entries.
// See: https://developer.mozilla.org/en-US/docs/Web/API/DirectoryReader#readEntries
} catch (err) {
_iterator7.e(err);
} finally {
_iterator7.f();
}
readEntries();
}
return null;
}, errorHandler);
};
Now passedFolders will contain an associate array, where the key is the pathname and the value is true if it is empty, false if it contained files.
Then under the dropzone init, when the file uploads are complete you can choose what to do with empty folders
this.on('queuecomplete', function (file, json) {
alert("Uploads complete, now we can deal with the empty folders");
for (var key in passedFolders) {
if (passedFolders[key]) {
alert(key);
}
}
});
In my previous question (MFC: how to change default file name for CFileDialog?), I overload DoSave function to supply a file name suggestion. Within this function is there way to differentiate "Save" vs "Save As"? Because I should only pop out dialog window if it is "Save As".
BOOL CMyDoc::DoSave(LPCTSTR lpszPathName, BOOL bReplace)
{
if (IsSaveAS() || m_saved_file_name.IsEmpty())
{
CString file_name = some_suggested_file_name;
CFileDialog file_dialog(false, ..., file_name, ...);
if (file_dialog.DoModal() == IDOK)
{
m_saved_file_name = file_dialog.GetPathName();
}
}
OnSaveDocument(m_saved_file_name);
return TRUE;
}
The dialog should be popped when lpszPathName == NULL || *lpszPathName == '\0'.
This covers the case where DoSave is called from OnFileSaveAs:
void CDocument::OnFileSaveAs()
{
if(!DoSave(NULL))
TRACE(traceAppMsg, 0, "Warning: File save-as failed.\n");
}
And also the case where the associated file is read-only and cannot be written to.
BOOL CDocument::DoFileSave()
{
DWORD dwAttrib = GetFileAttributes(m_strPathName);
if (dwAttrib & FILE_ATTRIBUTE_READONLY)
{
// we do not have read-write access or the file does not (now) exist
if (!DoSave(NULL))
{
TRACE(traceAppMsg, 0, "Warning: File save with new name failed.\n");
return FALSE;
}
}
else
{
if (!DoSave(m_strPathName))
{
TRACE(traceAppMsg, 0, "Warning: File save failed.\n");
return FALSE;
}
}
return TRUE;
}
It also matches the logic of the builtin DoSave.
BOOL CDocument::DoSave(LPCTSTR lpszPathName, BOOL bReplace)
// Save the document data to a file
// lpszPathName = path name where to save document file
// if lpszPathName is NULL then the user will be prompted (SaveAs)
// note: lpszPathName can be different than 'm_strPathName'
// if 'bReplace' is TRUE will change file name if successful (SaveAs)
// if 'bReplace' is FALSE will not change path name (SaveCopyAs)
{
CString newName = lpszPathName;
if (newName.IsEmpty())
{
// ...
if (!AfxGetApp()->DoPromptFileName(newName,
// ...
This is the event handlers i implemented to the copy, paste and Cut buttons in my MFCRibbonBar:
in the MyRibbonView.cpp:
void CMyRibbonView::OnEditCopy()
{
CWnd *wnd = GetFocus();
if (wnd == pEdit)
pEdit->Copy();
if (!OpenClipboard())
{
AfxMessageBox(_T("Cannot open the Clipboard"));
return;
}
if (!EmptyClipboard())
{
AfxMessageBox(_T("Cannot empty the Clipboard"));
return;
}
HGLOBAL hGlob = GlobalAlloc(GMEM_FIXED, 64);
strcpy_s((char*)hGlob, 64, "Current selection\r\n");
if (::SetClipboardData(CF_TEXT, hGlob) == NULL)
{
CString msg;
msg.Format(_T("Unable to set Clipboard data, error: %d"), GetLastError());
AfxMessageBox(msg);
CloseClipboard();
GlobalFree(hGlob);
return;
}
CloseClipboard();
}
void CMyRibbonView::OnEditPaste()
{
if (OpenClipboard())
{
HANDLE hClipboardData = GetClipboardData(CF_TEXT);
char *pchData = (char*)GlobalLock(hClipboardData);
CString strFromClipboard;
strFromClipboard = pchData;
pEdit->SetWindowText(strFromClipboard);
GlobalUnlock(hClipboardData);
CloseClipboard();
}
}
void CMyRibbonView::OnEditCut()
{
OnEditCopy();
pEdit->SetWindowText(L" ");
}
There is no errors, it's just not working. I tested it by adding the messages to check if it's actually the data or not but they're not popping up.
You need to GlobalLock your hGlob memory before copying your character string into it (this operation converts it into a usable pointer for your process - see here), and then call GlobalUnlock after you've done that (so that the clipboard can access hGlob):
HGLOBAL hGlob = GlobalAlloc(GMEM_FIXED, 64); // Maybe also need GMEM_MOVEABLE here instead?
char* cCopy = (char*)GlobalLock(hGlob);
strcpy_s(cGlob, 64, "Current selection\r\n");
GlobalUnlock(hGlob);
if (::SetClipboardData(CF_TEXT, hGlob) == NULL)
{
//...
And you'll need a similar arrangement for the paste operation.
I'm working on a drag and drop problem now and trying to get the PIDLs of the items being dragged from windows shell to my application.
The code below can get correct PIDLs if the dragged item is 'My Computer' or 'Control Panel' itself, but it doesn't work when the dragged item is an item in the 'Control Panel'.
What's wrong with my code?
#define GetPIDLFolder(pida) (LPCITEMIDLIST)(((LPBYTE)pida)+(pida)->aoffset[0])
#define GetPIDLItem(pida, i) (LPCITEMIDLIST)(((LPBYTE)pida)+(pida)->aoffset[i+1])
STGMEDIUM medium;
UINT fmt = RegisterClipboardFormat(CFSTR_SHELLIDLIST);
FORMATETC fe= {fmt, NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL};
HRESULT GETDATA_RESULT = pDataObject->GetData(&fe, &medium);
if (SUCCEEDED(GETDATA_RESULT))
{
LPIDA pida = (LPIDA)GlobalLock(medium.hGlobal);
LPCITEMIDLIST pidlFolder = GetPIDLFolder(pida);
int n = pida->cidl; // the n is always correct
if( n > 0 )
{
LPCITEMIDLIST pidlItem = GetPIDLItem(pida, 0);
// the pidlItem is wrong when the dragged object is an item in 'Control Panel'
}
GlobalUnlock(medium.hGlobal);
}
ReleaseStgMedium(&medium);
Any idea? Thanks
Zach#Shine
If I D&D Mouse, Network Connections and Fonts I get the following output in my test app:
0 Mouse | sfgao=4 hr=0
1 ::{20D04FE0-3AEA-1069-A2D8-08002B30309D}\::{21EC2020-3AEA-1069-A2DD-08002B30309D}\::{7007ACC7-3202-11D1-AAD2-00805FC1270E} | sfgao=20000004 hr=0
2 C:\WINDOWS\Fonts | sfgao=60000004 hr=0
The Network Connections and Fonts pidls can be converted to fully qualified shell paths while Mouse only returns a relative path/displayname.
This makes sense if you check the documentation for IShellFolder::GetDisplayNameOf:
...They do not guarantee that
IShellFolder will return the requested
form of the name. If that form is not
available, a different one might be
returned. In particular, there is no
guarantee that the name returned by
the SHGDN_FORPARSING flag will be
successfully parsed by
IShellFolder::ParseDisplayName. There
are also some combinations of flags
that might cause the
GetDisplayNameOf/ParseDisplayName
round trip to not return the original
identifier list. This occurrence is
exceptional, but you should check to
be sure.
It is clear that when dealing with controlpanel items, you need to keep the pidl around and only use GetDisplayNameOf for display strings in your UI.
(IShellLink::SetIDList on the Mouse pidl will create a working shortcut so the pidl is valid)
void OnDrop(IDataObject*pDO)
{
STGMEDIUM medium;
UINT fmt = RegisterClipboardFormat(CFSTR_SHELLIDLIST);
FORMATETC fe= {fmt, NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL};
HRESULT hr = pDO->GetData(&fe, &medium);
if (SUCCEEDED(hr))
{
LPIDA pida = (LPIDA)GlobalLock(medium.hGlobal);
if (pida)
{
LPCITEMIDLIST pidlFolder = GetPIDLFolder(pida);
for (UINT i=0; i<pida->cidl; ++i)
{
LPCITEMIDLIST pidlItem = GetPIDLItem(pida,i);
LPITEMIDLIST pidlAbsolute = ILCombine(pidlFolder,pidlItem);
if (pidlAbsolute)
{
IShellFolder*pParentSF;
hr= SHBindToParent(pidlAbsolute,IID_IShellFolder,(void**)&pParentSF,&pidlItem);
if (SUCCEEDED(hr))
{
STRRET str;
hr= pParentSF->GetDisplayNameOf(pidlItem, SHGDN_FORPARSING, &str);
if (SUCCEEDED(hr))
{
TCHAR szName[MAX_PATH];
hr= StrRetToBuf(&str,pidlItem,szName,MAX_PATH);
if (SUCCEEDED(hr))
{
SFGAOF sfgao = SFGAO_FOLDER|SFGAO_FILESYSTEM|SFGAO_HASSUBFOLDER|SFGAO_CANLINK;
hr= pParentSF->GetAttributesOf(1,&pidlItem,&sfgao);
TRACE(_T("%u %s | sfgao=%X hr=%X\n"),i,szName,sfgao,hr);
CreateTestShortcut(pidlAbsolute);
}
}
pParentSF->Release();
}
ILFree(pidlAbsolute);
}
}
GlobalUnlock(medium.hGlobal);
}
ReleaseStgMedium(&medium);
}
}
I'm trying to write some simple code that will return the directory for the recycle bin on a local drive. Seems like it would be simple -- should be a thousand answers on Google. Haven't found one yet :(
I HAVE found that FAT and NTFS drives have different base names (RECYCLED and RECYCLER). I've found that 'the' recycle bin is a virtual folder that combines the recycle bins of all drives on the machine.
What I haven't found is a way to find C: drive's recycle bin directory -- even on a Vietnamese (or any other non-English) machine. (No posts I can find indicate whether "RECYCLER" gets internationalized or not)
Can anyone point me to a definitive answer?
Thanks
UPDATE: Aware of CSIDL_BITBUCKET and the functions that use it. From everything I've read though, it points to a virtual directory which is the union of all deleted files by that user on all drives. Looking for the physical recycle bin directory (on my Vista it appears to be C:\$Recycle.Bin as far as I can tell)
Using Raymond Chen's advice, and someone else's technique (can't remember where I found it) I present a function that will find the Recycle Bin directory on a drive. The function cycles through the directories in the root directory looking at hidden and/or system directories. When it finds one, it checks the child subdirectories looking for one that has CLSID_Recycle Bin.
Note that I've included two GetFolderCLSID functions below. Raymond Chen's is the simpler one, but it doesn't work on Windows 2000. The other implementation is longer, but appears to work everywhere.
Call like: CString recycleDir = FindRecycleBinOnDrive(L"C:\");
CString FindRecycleBinOnDrive(LPCWSTR path)
{
CString search;
search.Format(L"%c:\\*", path[0]);
WIN32_FIND_DATA fd = {0};
HANDLE fHandle = FindFirstFile(search, &fd);
while(INVALID_HANDLE_VALUE != fHandle)
{
if(FILE_ATTRIBUTE_DIRECTORY == (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) //only check directories
{
if(0 != (fd.dwFileAttributes & (FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_HIDDEN))) //only check hidden and/or system directories
{
//the recycle bin directory itself won't be marked, but a SID-specific child directory will, so now look at them
CString childSearch;
childSearch.Format(L"%c:\\%s\\*", path[0], fd.cFileName);
WIN32_FIND_DATA childFD = {0};
HANDLE childHandle = FindFirstFile(childSearch, &childFD);
while(INVALID_HANDLE_VALUE != childHandle)
{
if((FILE_ATTRIBUTE_DIRECTORY == (childFD.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) && //only check directories
(childFD.cFileName[0] != L'.')) //don't check . and .. dirs
{
CString fullPath;
fullPath.Format(L"%c:\\%s\\%s", path[0], fd.cFileName, childFD.cFileName);
CLSID id = {0};
HRESULT hr = GetFolderCLSID(fullPath, id);
if(SUCCEEDED(hr))
{
if(IsEqualGUID(CLSID_RecycleBin, id))
{
FindClose(childHandle);
FindClose(fHandle);
//return the parent (recycle bin) directory
fullPath.Format(L"%c:\\%s", path[0], fd.cFileName);
return fullPath;
}
}
else
{
Log(logERROR, L"GetFolderCLSID returned %08X for %s", hr, fullPath);
}
}
if(FALSE == FindNextFile(childHandle, &childFD))
{
FindClose(childHandle);
childHandle = INVALID_HANDLE_VALUE;
}
}
}
}
if(FALSE == FindNextFile(fHandle, &fd))
{
FindClose(fHandle);
fHandle = INVALID_HANDLE_VALUE;
}
}
_ASSERT(0);
return L"";
}
//Works on Windows 2000, and even as Local System account
HRESULT GetFolderCLSID(LPCWSTR path, CLSID& pathCLSID)
{
LPMALLOC pMalloc = NULL;
HRESULT hr = 0;
if (SUCCEEDED(hr = SHGetMalloc(&pMalloc)))
{
LPSHELLFOLDER pshfDesktop = NULL;
if (SUCCEEDED(hr = SHGetDesktopFolder(&pshfDesktop)))
{
LPITEMIDLIST pidl = NULL;
DWORD dwAttributes = SFGAO_FOLDER;
if (SUCCEEDED(hr = pshfDesktop->ParseDisplayName(NULL, NULL, (LPWSTR)path, NULL, &pidl, &dwAttributes)))
{
LPPERSIST pPersist = NULL;
if (SUCCEEDED(hr = pshfDesktop->BindToObject(pidl, NULL, IID_IPersist, (LPVOID *) &pPersist)))
{
hr = pPersist->GetClassID(&pathCLSID);
pPersist->Release();
}
pMalloc->Free(pidl);
}
pshfDesktop->Release();
}
pMalloc->Release();
}
return hr;
}
//Not supported on Windows 2000 since SHParseDisplayName wasn't implemented then
//HRESULT GetFolderCLSID(LPCWSTR pszPath, CLSID& pathCLSID)
//{
// SHDESCRIPTIONID did = {0};
// HRESULT hr = 0;
// LPITEMIDLIST pidl = NULL;
// if (SUCCEEDED(hr = SHParseDisplayName(pszPath, NULL, &pidl, 0, NULL))) //not supported by Windows 2000
// {
// IShellFolder *psf = NULL;
// LPCITEMIDLIST pidlChild = NULL;
// if (SUCCEEDED(hr = SHBindToParent(pidl, IID_IShellFolder, (void**)&psf, &pidlChild)))
// {
// hr = SHGetDataFromIDList(psf, pidlChild, SHGDFIL_DESCRIPTIONID, &did, sizeof(did));
// psf->Release();
// pathCLSID = did.clsid;
// }
// CoTaskMemFree(pidl);
// }
// return hr;
//}
In Win32, use SHGetSpecialFolderLocation. Pass CSIDL_BITBUCKET as the CDIL parameter.
A little bit late, but perhaps better late than never...
After debugging shell32.dll, I have found that for each version of windows the recycle path is hardcoded and, also, depends on the filesystem of that drive. I have tested this on Windows XP, Vista and Windows7:
Let X: be the drive we want to get the path to the recycle bin and let SID be the SID of the current user, then:
switchif(OsType) {
case WindowsXP:
{
if(PartitionType("X:") == NTFS)
{
printf("Path is: X:\\Recycler\\SID\\");
}
else
{
printf("Path is X:\\RECYCLED\\");
}
}
case WindowsVista:
case Windows7:
{
if(PartitionType("X:") == NTFS)
{
printf("Path is: X:\\$Recycle.bin\\SID\\");
}
else
{
printf("Path is X:\\$RECYCLE.BIN\\");
}
}
}
A wiki article presents the same facts:
http://en.wikipedia.org/wiki/Recycle_Bin_%28Windows%29