Skip to content

Instantly share code, notes, and snippets.

@markdevel
Created October 30, 2025 12:10
Show Gist options
  • Save markdevel/47d40a16ab9b6fae7029d948aca095d0 to your computer and use it in GitHub Desktop.
Save markdevel/47d40a16ab9b6fae7029d948aca095d0 to your computer and use it in GitHub Desktop.
#
# Logi M750 Programally EasySwitch
#
# <<< Be sure to save it in UTF-16 >>>
#
param(
[Parameter(Mandatory=$true)]
[ValidateSet(0,1,2)]
[int]$Value
)
$mice = Get-PnpDevice -PresentOnly | Where-Object { $_.Class -eq "Mouse" }
if (-not $mice) {
Write-Host "No connected USB mouse was found."
return
}
Add-Type -TypeDefinition @"
using System;
using System.Runtime.InteropServices;
using Microsoft.Win32.SafeHandles;
public class HidHelper {
[StructLayout(LayoutKind.Sequential)]
public struct SP_DEVICE_INTERFACE_DATA {
public int cbSize;
public Guid InterfaceClassGuid;
public int Flags;
public IntPtr Reserved;
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
public struct SP_DEVICE_INTERFACE_DETAIL_DATA {
public int cbSize;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 256)]
public string DevicePath;
}
[StructLayout(LayoutKind.Sequential)]
public struct HIDP_CAPS {
public short Usage;
public short UsagePage;
public short InputReportByteLength;
public short OutputReportByteLength;
public short FeatureReportByteLength;
[MarshalAs(UnmanagedType.ByValArray, SizeConst=17)]
public short[] Reserved;
public short NumberLinkCollectionNodes;
public short NumberInputButtonCaps;
public short NumberInputValueCaps;
public short NumberInputDataIndices;
public short NumberOutputButtonCaps;
public short NumberOutputValueCaps;
public short NumberOutputDataIndices;
public short NumberFeatureButtonCaps;
public short NumberFeatureValueCaps;
public short NumberFeatureDataIndices;
}
[DllImport("hid.dll")]
public static extern void HidD_GetHidGuid(out Guid HidGuid);
[DllImport("setupapi.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern IntPtr SetupDiGetClassDevs(
ref Guid ClassGuid,
IntPtr Enumerator,
IntPtr hwndParent,
uint Flags);
[DllImport("setupapi.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern bool SetupDiEnumDeviceInterfaces(
IntPtr hDevInfo,
IntPtr devInfo,
ref Guid interfaceClassGuid,
uint memberIndex,
ref SP_DEVICE_INTERFACE_DATA deviceInterfaceData);
[DllImport("setupapi.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern bool SetupDiGetDeviceInterfaceDetail(
IntPtr hDevInfo,
ref SP_DEVICE_INTERFACE_DATA deviceInterfaceData,
ref SP_DEVICE_INTERFACE_DETAIL_DATA deviceInterfaceDetailData,
int deviceInterfaceDetailDataSize,
out int requiredSize,
IntPtr deviceInfoData);
[DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)]
public static extern SafeFileHandle CreateFile(
string lpFileName,
uint dwDesiredAccess,
uint dwShareMode,
IntPtr lpSecurityAttributes,
uint dwCreationDisposition,
uint dwFlagsAndAttributes,
IntPtr hTemplateFile);
[DllImport("hid.dll", SetLastError = true)]
public static extern bool HidD_SetOutputReport(
SafeFileHandle HidDeviceObject,
byte[] ReportBuffer,
int ReportBufferLength);
[DllImport("hid.dll", SetLastError = true)]
public static extern bool HidD_GetPreparsedData(
SafeFileHandle hObject,
out IntPtr PreparsedData);
[DllImport("hid.dll", SetLastError = true)]
public static extern bool HidD_FreePreparsedData(IntPtr PreparsedData);
[DllImport("hid.dll", SetLastError = true)]
public static extern int HidP_GetCaps(
IntPtr PreparsedData,
out HIDP_CAPS Capabilities);
const uint DIGCF_PRESENT = 0x2;
const uint DIGCF_DEVICEINTERFACE = 0x10;
const uint GENERIC_READ = 0x80000000;
const uint GENERIC_WRITE = 0x40000000;
const uint OPEN_EXISTING = 3;
const uint FILE_FLAG_OVERLAPPED = 0x40000000;
const uint FILE_SHARE_READ = 1;
const uint FILE_SHARE_WRITE = 2;
public static string FindMatchingHidPath(string vid, string pid, short targetUsagePage, short targetUsage) {
Guid hidGuid;
HidD_GetHidGuid(out hidGuid);
IntPtr hDevInfo = SetupDiGetClassDevs(ref hidGuid, IntPtr.Zero, IntPtr.Zero,
DIGCF_PRESENT | DIGCF_DEVICEINTERFACE);
SP_DEVICE_INTERFACE_DATA da = new SP_DEVICE_INTERFACE_DATA();
da.cbSize = Marshal.SizeOf(da);
uint index = 0;
while (SetupDiEnumDeviceInterfaces(hDevInfo, IntPtr.Zero, ref hidGuid, index, ref da)) {
SP_DEVICE_INTERFACE_DETAIL_DATA dd = new SP_DEVICE_INTERFACE_DETAIL_DATA();
dd.cbSize = IntPtr.Size == 8 ? 8 : 4 + Marshal.SystemDefaultCharSize;
int required;
SetupDiGetDeviceInterfaceDetail(hDevInfo, ref da, ref dd, Marshal.SizeOf(dd), out required, IntPtr.Zero);
string path = dd.DevicePath;
if (path.ToLower().Contains("vid_" + vid.ToLower()) &&
path.ToLower().Contains("pid_" + pid.ToLower())) {
using (SafeFileHandle handle = CreateFile(path, GENERIC_READ|GENERIC_WRITE, FILE_SHARE_READ|FILE_SHARE_WRITE, IntPtr.Zero, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, IntPtr.Zero)) {
if (!handle.IsInvalid) {
IntPtr preparsed;
if (HidD_GetPreparsedData(handle, out preparsed)) {
HIDP_CAPS caps;
HidP_GetCaps(preparsed, out caps);
HidD_FreePreparsedData(preparsed);
if (caps.UsagePage == targetUsagePage && caps.Usage == targetUsage) {
return path;
}
}
}
}
}
index++;
}
return null;
}
public static void SendReport(string devicePath, byte[] report) {
using (SafeFileHandle handle = CreateFile(devicePath, GENERIC_READ|GENERIC_WRITE, FILE_SHARE_READ|FILE_SHARE_WRITE, IntPtr.Zero, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, IntPtr.Zero)) {
if (handle.IsInvalid) {
throw new System.ComponentModel.Win32Exception(Marshal.GetLastWin32Error(), "Could not open the device.");
}
if (!HidD_SetOutputReport(handle, report, report.Length)) {
throw new System.ComponentModel.Win32Exception(Marshal.GetLastWin32Error(), "Switching failed.");
}
}
}
}
"@
foreach ($mouse in $mice) {
Write-Host $mouse.InstanceId
if ($mouse.InstanceId -match "VID_([0-9A-F]{4})&PID_([0-9A-F]{4})") {
$vid = $matches[1]
$usbPid = $matches[2]
Write-Host "Target: $($mouse.FriendlyName) (VID=$vid, PID=$usbPid)"
# Search for an interface with UsagePage=0xFF00 and Usage=0x0001
$path = [HidHelper]::FindMatchingHidPath($vid, $usbPid, -256, 1)
if ($path) {
$report = [byte[]](0x10,0x02,0x0a,0x1b,$Value,0x00,0x00)
try {
[HidHelper]::SendReport($path, $report)
Write-Host "Switching successful"
}
catch {
Write-Host "Error: $($_.Exception.Message)"
}
}
else {
Write-Host "The interface could not be found."
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment