microsoft / CsWin32

A source generator to add a user-defined set of Win32 P/Invoke methods and supporting types to a C# project.
MIT License
2.05k stars 85 forks source link

Unable to access properties of COM objects (e.g. Folder, FolderItem etc.) #964

Open fgimian opened 1 year ago

fgimian commented 1 year ago

Actual behavior

In attempting to obtain a list of Quick Access folder paths using Shell and IShellDispatch, accessing any FolderItem properties results in an exception.

Expected behavior

I've implemented similar logic using windows-rs and in Go too and in both cases, the properties return the appropriate values correctly.

Repro steps

  1. NativeMethods.txt content:

    Shell
    IShellDispatch
    Folder
    FolderItems
    FolderItem
    FolderItemVerbs
    FolderItemVerb
  2. NativeMethods.json content (if present): N/A

  3. Any of your own code that should be shared?

using Windows.Win32.UI.Shell;

Shell shell = new();
IShellDispatch shellDispatch = (IShellDispatch)shell;
var qaFolder = shellDispatch.NameSpace("shell:::{679f85cb-0220-4080-b29b-5540cc05aab6}");
var qaFolderItems = qaFolder.Items();

for (var i = 0; i < qaFolderItems.Count; i++)
{
    var qaFolderItem = qaFolderItems.Item(i);
    Console.WriteLine(qaFolderItem.Path);
}

Exception output is as follows:

Unhandled exception. System.InvalidCastException: OleAut reported a type mismatch.
   at Microsoft.Win32.OAVariantLib.ChangeTypeEx(Variant& result, Variant& source, Int32 lcid, IntPtr typeHandle, Int32 cvType, Int16 flags)
   at Microsoft.Win32.OAVariantLib.ChangeType(Variant source, Type targetClass, Int16 options, CultureInfo culture)
   at System.OleAutBinder.ChangeType(Object value, Type type, CultureInfo cultureInfo)
   at System.RuntimeType.ForwardCallToInvokeMember(String memberName, BindingFlags flags, Object target, Object[] aArgs, Boolean[] aArgsIsByRef, Int32[] aArgsWrapperTypes, Type[] aArgsTypes, Type retType)
   at Windows.Win32.UI.Shell.FolderItem.get_Path()
   at Program.<Main>$(String[] args) in C:\Users\Fots\source\QuickAccess\Program.cs:line 11

Context

Thanks heaps Fotis

fgimian commented 1 year ago

P.S.: My ultimate goal here is to update my Quick Access paths if they differ from the desired set of paths.

Something like this:

using Windows.Win32.UI.Shell;

var paths = new[]
{
    @"C:\ProgramData",
    @"C:\Users\Fots",
    @"C:\Users\Fots\AppData\Local",
    @"C:\Users\Fots\AppData\Roaming",
    @"C:\Users\Fots\Documents",
    @"C:\Users\Fots\Downloads",
    @"C:\Users\Fots\Music",
    @"C:\Users\Fots\Pictures",
    @"C:\Users\Fots\Source",
    @"C:\Users\Fots\Videos",
};

Shell shell = new();
IShellDispatch shellDispatch = (IShellDispatch)shell;
var qaFolder = shellDispatch.NameSpace("shell:::{679f85cb-0220-4080-b29b-5540cc05aab6}");
var qaFolderItems = qaFolder.Items();

var pinnedPaths = new List<string>();
for (var i = 0; i < qaFolderItems.Count; i++)
{
    var qaFolderItem = qaFolderItems.Item(i);

    if (qaFolderItem.IsFolder == 0)
    {
        continue;
    }

    var qaFolderItemVerbs = qaFolderItem.Verbs();
    var pinned = false;
    for (int j = 0; j < qaFolderItemVerbs.Count; j++)
    {
        var qaFolderItemVerb = qaFolderItemVerbs.Item(j);
        if (qaFolderItemVerb.Name.ToString() == "Unpin from Quick access")
        {
            pinned = true;
            break;
        }
    }

    if (pinned)
    {
        pinnedPaths.Add(qaFolderItem.Path.ToString());
    }
}

if (!pinnedPaths.SequenceEqual(paths))
{
    for (var i = 0; i < qaFolderItems.Count; i++)
    {
        var qaFolderItem = qaFolderItems.Item(i);
        qaFolderItem.InvokeVerb("unpinfromhome");
    }

    foreach (var path in paths)
    {
        var folder = shellDispatch.NameSpace(path);
        var folder2 = (Folder2)folder;
        folder2.Self.InvokeVerb("pintohome");
    }
}