Open sub-subfolder in Windows namespace extension - winapi

I'm implementing a Basic Folder Object for a namespace extension composed by folders and subfolders (the junction point is a filesystem folder, which is empty). I've implemented IShellFolder, and support returning IContextMenu in ShellFolderImpl::GetUIObjectOf.
Suppose that I have the following folders (where A and A\B are "virtual" folders fabricated by IShellFolder::EnumObjects)
C:\test.{74660590-4BEF-4BF4-9C85-5FAE0E084926}
C:\test.{74660590-4BEF-4BF4-9C85-5FAE0E084926}\A
C:\test.{74660590-4BEF-4BF4-9C85-5FAE0E084926}\A\B
I'm able to open C:\test.{74660590-4BEF-4BF4-9C85-5FAE0E084926}, and it lists the folder A. When I double-click (or select Open from context menu) on folder A, that folder is opened and subfolder B is listed in the view.
Problem: that only works for folders directly under C:\test.{74660590-4BEF-4BF4-9C85-5FAE0E084926}. The context menu is not displayed for subfolder B (and GetUIObjectOf is never called, even though IShellFolder:Initialize is called with the right PIDL).
In the relevant part of IContextMenu::InvokeCommand, I open the subfolder by doing
SHELLEXECUTEINFO sei = { 0 };
sei.cbSize = sizeof(sei);
sei.fMask = SEE_MASK_IDLIST | SEE_MASK_CLASSNAME;
sei.lpIDList = pidl; // the fully qualified PIDL
sei.lpClass = TEXT("folder");
sei.hwnd = pcmi->hwnd;
sei.nShow = pcmi->nShow;
sei.lpVerb = cmd.verb.w_str();
BOOL bRes = ::ShellExecuteEx(&sei);
DeletePidl(pidl);
return bRes?S_OK:HR(HRESULT_FROM_WIN32(GetLastError()));

Related

IShellLink - how to get the original target path

I created a shortcut in a Windows PC with a target path of:
C:\Users\b\Desktop\New Text Document.txt
Then I copied the shortcut to another PC with a different user name, and I want to retrieve the original target path.
If you open the shortcut file with a text editor, you can see the original path is preserved, so the goal is definitely possible.
The following code does not work, despite the presence of SLGP_RAWPATH. It outputs:
C:\Users\a\Desktop\New Text Document.txt
It is changing the user folder name to the one associated with the running program.
I understand that the problem is not about environment variables, because no environment variable name can be seen in the file. But I can't find any documentation about this auto-relocation behavior.
IShellLinkW*lnk;
if (CoCreateInstance(CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER, IID_IShellLinkW, (LPVOID*)&lnk) == 0){
IPersistFile* file;
if (lnk->QueryInterface(IID_IPersistFile, (void**)&file) == 0){
if (file->Load(L"shortcut", 0) == 0){
wchar_t path[MAX_PATH];
if (lnk->GetPath(path, _countof(path), 0, SLGP_RAWPATH) == 0){
_putws(path);
}
IShellLinkDataList* sdl;
if (lnk->QueryInterface(IID_IShellLinkDataList, (void**)&sdl) == 0){
EXP_SZ_LINK* lnkData;
if (sdl->CopyDataBlock(EXP_SZ_LINK_SIG, (void**)&lnkData) == 0){
_putws(lnkData->swzTarget);
LocalFree(lnkData);
}
sdl->Release();
}
}
file->Release();
}
lnk->Release();
}
The Windows Shell Link class implements a property store, so you can get access to this with code like this (with ATL smart pointers):
int main()
{
// note: error checking omitted!
CoInitialize(NULL);
{
CComPtr<IShellLink> link;
link.CoCreateInstance(CLSID_ShellLink);
CComPtr<IPersistFile> file;
link->QueryInterface(&file);
file->Load(L"shortcut", STGM_READ);
// get the property store
CComPtr<IPropertyStore> ps;
link->QueryInterface(&ps);
// dump all properties
DWORD count = 0;
ps->GetCount(&count);
for (DWORD i = 0; i < count; i++)
{
PROPERTYKEY pk;
ps->GetAt(i, &pk);
// get property's canonical name from pk
CComHeapPtr<wchar_t> name;
PSGetNameFromPropertyKey(pk, &name);
PROPVARIANT pv;
PropVariantInit(&pv);
ps->GetValue(pk, &pv);
// convert PropVariants to a string to be able to display
CComHeapPtr<wchar_t> valueAsString;
PropVariantToStringAlloc(pv, &valueAsString); // propvarutil.h
wprintf(L"%s: %s\n", name, valueAsString);
PropVariantClear(&pv);
}
}
CoUninitialize();
return 0;
}
It will output this:
System.ItemNameDisplay: New Text Document.txt
System.DateCreated: 2021/06/03:14:45:30.000
System.Size: 0
System.ItemTypeText: Text Document
System.DateModified: 2021/06/03:14:45:29.777
System.ParsingPath: C:\Users\b\Desktop\New Text Document.txt
System.VolumeId: {E506CEB2-0000-0000-0000-300300000000}
System.ItemFolderPathDisplay: C:\Users\b\Desktop
So, you're looking for System.ParsingPath, which you can get directly like this:
...
ps->GetValue(PKEY_ParsingPath, &pv); // propkey.h
...
Your shortcut is a .lnk file, just without the .lnk file extension present. According to Microsoft's latest "Shell Link (.LNK) Binary File Format" documentation, your shortcut appears to be configured as a relative file target. The relative name is just New Text Document.txt. I didn't dig into the file too much, but I'm guessing that it is relative to the system's Desktop folder, so it will take on whatever the actual Desktop folder of the current PC is. Which would explain why querying the target changes the relative root from C:\Users\b\Desktop to C:\Users\a\Desktop when you change PCs.
As for being able to query the original target C:\Users\b\Desktop\New Text Document.txt, that I don't know. It is also present in the file, so in theory there should be a way to query it, but I don't know which field it is in, without taking the time to fully decode this file. You should try writing your own decoder, using the above documentation.

Download bulk files drom Google Drive with adjusted parent Folder

I would like to download from my Google Drive.
The folders structure is quite complicated. Here is the overview:
Parent Folder 1
--- Parent Folder 2
------ Parent Folder 3
--------- Parent Folder 4
------------ Parent Folder 5
--------------- Data
is there any script to download files with the folder structure like this:
Parent Folder 2
--- Parent Folder 3
------ Parent Folder 5
--------- Data
Thanks
I have done this in the past what you will need to do is create a recursive method. First get a list of all the files on your google drive account. Then sort them by the name if the file has a folder then create that folder. Then Find all the files within that folder and list them. You can preform a download once on the file once you have found the correct directory it should be in.
This is my example using C#.
public static void PrettyPrint(DriveService service, Google.Apis.Drive.v3.Data.FileList list, string indent)
{
foreach (var item in list.Files.OrderBy(a => a.Name))
{
Console.WriteLine(string.Format("{0}|-{1}", indent, item.Name));
if (item.MimeType == "application/vnd.google-apps.folder")
{
var ChildrenFiles = ListAll(service, new FilesListOptionalParms { Q = string.Format("('{0}' in parents)", item.Id), PageSize = 1000 });
PrettyPrint(service, ChildrenFiles, indent + " ");
}
}
}
My full tutorial on how to do this in C# can be found her List all files

JXA: Create a mailbox in Apple Mail

I am trying to create a sub-mailbox in Apple Mail using JavaScript.
I have the following code snippet (parent is a reference to the mailbox in which I want the new mailbox):
var mb = mail.Mailbox({name: "SubFolder"});
parent.mailboxes.push(mb);
The events log shows:
app = Application("Mail")
app.mailboxes.byName("Local").mailboxes.byName("Archive").mailboxes.push(app.Mailbox({"name":"SubFolder"}))
--> Error -10000: AppleEvent handler failed.
What am I doing wrong?
Thanks,
Craig.
Code now:
var mb = mail.Mailbox({name: "Local/Archive/Test Archive/SubFolder"})
logger.logDebug("mb = '" + Automation.getDisplayString(mb) + "'.");
mail.mailboxes.push(mb) // create the subfolder
This works as long as there are no spaces in the path.
I tried to force the space using \\ in front of it, but then you get "Test\ Archive" as the name.
So how do I get a space in the name to work?
Thanks.
To create a sub-folder, you need a name like a posix path --> "/theMasterMailbox/subMailBox1/subMailBox2/subMailBox3".
So, you need:
A loop to put the name of each parent folder into an array.
Use join('/') to join the elements of an array into a string.
Use mail.mailboxes.push(mb) instead of parent.mailboxes.push(mb)
Here's a sample script which creates a mailbox named "SubFolder" in the selected folder (the mailbox):
mail = Application('com.apple.Mail')
parent = mail.messageViewers()[0].selectedMailboxes()[0]
mboxNames = [parent.name()]
thisFolder = parent
try {
while (true) { // loop while exists the parent folder
mboxNames.unshift(thisFolder.container().name()) // add the name of the parent folder to the beginning of an array
thisFolder = thisFolder.container() // get the parent of thisFolder
}
} catch (e) {} // do nothing on error, because thisFolder is the top folder
mboxNames.push("SubFolder") // append the name of the new subFolder to the array
mBoxPath = mboxNames.join('/') // get a string (the names separated by "/")
mb = mail.Mailbox({name:mBoxPath})
mail.mailboxes.push(mb) // create the subfolder

How to programmatically add a folder to Favorites in Windows File Explorer

I am aware of the solutions answered here. Basically the idea is to create a link to folder in the %USERPROFILE%\Favoriates folder.
However it doesn't work for me. I'm using Windows8 (don't know if that matters). In my %USERPROFILE%\Favoriates, it contains favoriate items for IE, not the file explorer.
I tried to locate this settings in registry and file system by creating a folder with very unique name and adding it to file explorer favoriates. Then search for the name in registry and file system. Didn't yield anything.
It looks like you want %UserProfile%\Links.
Programmatically, you want to retrieve the location using SHGetKnownFolderPath with KNOWNFOLDERID_Links, instead of hard-coding any location, and then use IShellLink to create the shortcut file in that location.
Here is a C# example for the first part:
[DllImport("shell32.dll")]
static extern int SHGetKnownFolderPath(
[MarshalAs(UnmanagedType.LPStruct)] Guid knownFolderId,
uint flags,
IntPtr userToken,
[MarshalAs(UnmanagedType.LPWStr)] out string knownFolderPath);
// this corresponds to the KNOWNFOLDERID_Links constant:
public static readonly Guid Links = new Guid("bfb9d5e0-c6a9-404c-b2b2-ae6db6af4968");
public static string GetKnownFolderPath(Guid knownFolderId)
{
string path;
int result = SHGetKnownFolderPath(knownFolderId, 0, IntPtr.Zero, out path);
// … (error handling; check result for E_FAIL, E_INVALIDARG, or S_OK)
return path;
}
Ah, looks like for Windows 8 this location has changed to %USERPROFILE%\Links, rather than %USERPROFILE%\Favoriates.
So to answer my question. To programmically add a folder to the Favoriates in Windows 8 file explorer, you need to make a link to that folder in the %USERPROFILE%\Links folder:
mklink /D %USERPROFILE%\Links\<Link_Name> <Tartget_Folder_Path>
The Explorer Favorites are stored here %USERPROFILE%\Links.
Function AddAFolderShortCut($fileName, $targetPath)
{
Write-Host "Creating Shortcut $fileName points to $targetPath"
$WshShell = New-Object -comObject WScript.Shell
$Shortcut = $WshShell.CreateShortcut("$env:USERPROFILE\Links\$fileName.lnk")
$Shortcut.TargetPath = $targetPath
$Shortcut.Save()
}
AddAFolderShortCut "FolderName" "C:\folderpath"

create a folder in a grandparent dir vb.net

I want to create a dir (named with a varible Utilities._Name)located two levels from the exe file,
My exe file is in C:\SGEA\SGEA\bin
How can I do it so I get C:\SGEA\theNewDir without using full path, just relative paths?
If Not System.IO.Directory.Exists(Utilities._Name) Then
System.IO.Directory.CreateDirectory(Utilities._Name)
Else
MessageBox.Show("There is already a dir named: " & Utilities._Name, "SGEA", MessageBoxButtons.OK, MessageBoxIcon.Information)
End If
If you have the path to the exe file, you can use the System.IO.Path Class to navigate easily:
Dim folder = Path.GetDirectoryName(theExeFile)
Dim grandparent = Path.GetDirectoryName(Path.GetDirectoryName(folder)) ' Up two directories
Dim newFolder = Path.Combine(grandparent, "theNewDir") ' Use this to create the new folder name cleanly
Utilities._Name = newFolder

Resources