See the question and my original answer on StackOverflow

Here 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);
        }
    }
}