param(
  [string]$InputFile = "",
  [string]$DeviceHint = "xprinter|xp-420|xp420|dxp|420"
)

if (-not (Test-Path $InputFile)) {
  Write-Host "ERR_INPUT_NOT_FOUND"
  exit 1
}

$source = @"
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.IO;
using System.Runtime.InteropServices;
using Microsoft.Win32.SafeHandles;

public static class UsbPrinterRaw {
  private static readonly Guid UsbPrinterGuid = new Guid("28D78FAD-5A12-11D1-AE5B-0000F803A8C2");
  private const int DIGCF_PRESENT = 0x00000002;
  private const int DIGCF_DEVICEINTERFACE = 0x00000010;
  private const uint GENERIC_WRITE = 0x40000000;
  private const uint FILE_SHARE_READ = 0x00000001;
  private const uint FILE_SHARE_WRITE = 0x00000002;
  private const uint OPEN_EXISTING = 3;
  private const uint FILE_ATTRIBUTE_NORMAL = 0x00000080;

  [StructLayout(LayoutKind.Sequential)]
  private struct SP_DEVICE_INTERFACE_DATA {
    public int cbSize;
    public Guid InterfaceClassGuid;
    public int Flags;
    public IntPtr Reserved;
  }

  [DllImport("setupapi.dll", SetLastError = true)]
  private static extern IntPtr SetupDiGetClassDevs(ref Guid ClassGuid, IntPtr Enumerator, IntPtr hwndParent, int Flags);

  [DllImport("setupapi.dll", SetLastError = true)]
  private static extern bool SetupDiEnumDeviceInterfaces(IntPtr DeviceInfoSet, IntPtr DeviceInfoData, ref Guid InterfaceClassGuid, int MemberIndex, ref SP_DEVICE_INTERFACE_DATA DeviceInterfaceData);

  [DllImport("setupapi.dll", SetLastError = true, CharSet = CharSet.Auto)]
  private static extern bool SetupDiGetDeviceInterfaceDetail(IntPtr DeviceInfoSet, ref SP_DEVICE_INTERFACE_DATA DeviceInterfaceData, IntPtr DeviceInterfaceDetailData, int DeviceInterfaceDetailDataSize, out int RequiredSize, IntPtr DeviceInfoData);

  [DllImport("setupapi.dll", SetLastError = true)]
  private static extern bool SetupDiDestroyDeviceInfoList(IntPtr DeviceInfoSet);

  [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)]
  private static extern SafeFileHandle CreateFile(string lpFileName, uint dwDesiredAccess, uint dwShareMode, IntPtr lpSecurityAttributes, uint dwCreationDisposition, uint dwFlagsAndAttributes, IntPtr hTemplateFile);

  public static string[] ListDevicePaths() {
    var paths = new List<string>();
    Guid guid = UsbPrinterGuid;
    IntPtr set = SetupDiGetClassDevs(ref guid, IntPtr.Zero, IntPtr.Zero, DIGCF_PRESENT | DIGCF_DEVICEINTERFACE);
    if (set == IntPtr.Zero || set.ToInt64() == -1) return paths.ToArray();

    try {
      for (int index = 0; ; index++) {
        var data = new SP_DEVICE_INTERFACE_DATA();
        data.cbSize = Marshal.SizeOf(typeof(SP_DEVICE_INTERFACE_DATA));
        if (!SetupDiEnumDeviceInterfaces(set, IntPtr.Zero, ref guid, index, ref data)) break;

        int required = 0;
        SetupDiGetDeviceInterfaceDetail(set, ref data, IntPtr.Zero, 0, out required, IntPtr.Zero);
        if (required <= 0) continue;

        IntPtr detail = Marshal.AllocHGlobal(required);
        try {
          Marshal.WriteInt32(detail, IntPtr.Size == 8 ? 8 : 6);
          if (!SetupDiGetDeviceInterfaceDetail(set, ref data, detail, required, out required, IntPtr.Zero)) continue;
          IntPtr pathPtr = IntPtr.Add(detail, 4);
          string path = Marshal.PtrToStringAuto(pathPtr);
          if (!String.IsNullOrWhiteSpace(path)) paths.Add(path);
        } finally {
          Marshal.FreeHGlobal(detail);
        }
      }
    } finally {
      SetupDiDestroyDeviceInfoList(set);
    }

    return paths.ToArray();
  }

  public static void WriteBytes(string devicePath, byte[] bytes) {
    using (SafeFileHandle handle = CreateFile(devicePath, GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, IntPtr.Zero, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, IntPtr.Zero)) {
      if (handle.IsInvalid) throw new Win32Exception(Marshal.GetLastWin32Error());
      using (FileStream stream = new FileStream(handle, FileAccess.Write)) {
        stream.Write(bytes, 0, bytes.Length);
        stream.Flush();
      }
    }
  }
}
"@

Add-Type -TypeDefinition $source -ErrorAction Stop

$paths = [UsbPrinterRaw]::ListDevicePaths()
if (-not $paths -or $paths.Count -eq 0) {
  Write-Host "ERR_USB_PRINTER_NOT_FOUND"
  exit 1
}

$selected = $null
foreach ($path in $paths) {
  if ($path -match $DeviceHint) {
    $selected = $path
    break
  }
}
if (-not $selected) {
  $selected = $paths[0]
}

try {
  $bytes = [System.IO.File]::ReadAllBytes($InputFile)
  [UsbPrinterRaw]::WriteBytes($selected, $bytes)
  Write-Host ("OK " + $selected)
  exit 0
}
catch {
  Write-Host ("ERR_USB_WRITE " + $_.Exception.Message)
  exit 1
}
