C#: How to open Windows Explorer windows with a number of files selected
See the question and my original answer on StackOverflowHere is a .NET Core version that uses CsWin32 nuget for interop code generation. At Shell level it uses IShellItem and IParentAndItem interfaces that are much easier to work with than IShellFolder
.
Since it's using IShellItem
and not necessarily physical files, this code
ShellUtilities.OpenFoldersAndSelectFiles(
[
@"c:\",
@"d:\",
]);
will select c: and d: in "This PC" shell namespace.
C# .csproj
file:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Windows.CsWin32" Version="0.3.106">
<PrivateAssets>all</PrivateAssets>
</PackageReference>
</ItemGroup>
</Project>
NativeMethods.txt
file that define what Win32 interfaces and functions we want to import
IShellItem
IParentAndItem
SHOpenFolderAndSelectItems
SHCreateItemFromParsingName
Sample program.cs
file:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using Windows.Win32;
using Windows.Win32.UI.Shell;
using Windows.Win32.UI.Shell.Common;
[assembly: SupportedOSPlatform("windows6.0.6000")]
namespace ConsoleApp;
internal class Program
{
static void Main()
{
ShellUtilities.OpenFoldersAndSelectFiles(
[
@"z:\Music\Thursday Blues\01. I wish it was friday.mp3",
@"z:\Music\Counting Sheep\01. Sheep #1.mp3",
@"z:\Music\Counting Sheep\02. Sheep #2.mp3"
]);
// select c: and d: in "This PC"
ShellUtilities.OpenFoldersAndSelectFiles(
[
@"c:\",
@"d:\",
]);
}
}
public static class ShellUtilities
{
public static unsafe void OpenFoldersAndSelectFiles(IEnumerable<string> files)
{
ArgumentNullException.ThrowIfNull(files);
// get all items & group by folder (path)
var items = Item.FromPaths(files);
foreach (var group in items.GroupBy(g => g.ToString()))
{
var childPidls = group.Select(g => (nint)g.ChildPidl).ToArray();
var pidls = Unsafe.AsPointer(ref MemoryMarshal.GetArrayDataReference(childPidls));
PInvoke.SHOpenFolderAndSelectItems(group.First().ParentPidl, (uint)group.Count(), (ITEMIDLIST**)pidls, 0);
}
items.ForEach(i => i.Dispose());
}
private unsafe sealed class Item : IDisposable
{
public char* ParentPath;
public ITEMIDLIST* ParentPidl;
public ITEMIDLIST* ChildPidl;
public override string ToString() => new(ParentPath);
public static List<Item> FromPaths(IEnumerable<string> files) // return list cause unsafe code cannot appear in iterators
{
var list = new List<Item>();
foreach (var file in files)
{
// build IShellItem
if (PInvoke.SHCreateItemFromParsingName(file, null, typeof(IShellItem).GUID, out var obj).Failed)
continue;
var item = (IShellItem)obj;
item.GetParent(out var parent);
parent.GetDisplayName(SIGDN.SIGDN_DESKTOPABSOLUTEPARSING, out var path);
// get parent & item relative pidls
ITEMIDLIST* childPidl; ITEMIDLIST* parentPidl;
((IParentAndItem)item).GetParentAndItem(&parentPidl, null, &childPidl);
list.Add(new Item { ParentPath = path.Value, ParentPidl = parentPidl, ChildPidl = childPidl });
}
return list;
}
public void Dispose()
{
if (ParentPath != null) { Marshal.FreeCoTaskMem((nint)ParentPath); ParentPath = null; }
if (ParentPidl != null) Marshal.FreeCoTaskMem((nint)ParentPidl); ParentPidl = null;
if (ChildPidl != null) Marshal.FreeCoTaskMem((nint)ChildPidl); ChildPidl = null;
GC.SuppressFinalize(this);
}
}
}