Created
October 30, 2025 12:10
-
-
Save markdevel/47d40a16ab9b6fae7029d948aca095d0 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # | |
| # 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