xlogI125’s blog

パソコン作業を効率化したい

PowerShell: フォームにドラッグ&ドロップされたファイルのパスを取得

マウスボタンの接触不良が原因でショートカット(*.lnk)アイコンへのドラッグ&ドロップ操作を失敗すると大変なことになるので、フォームへのドラッグ&ドロップを考える。

# PowerShell 5.1, Windows 11 (2024年2月頃)

$ErrorActionPreference = [System.Management.Automation.ActionPreference]::Stop
$VerbosePreference = [System.Management.Automation.ActionPreference]::Continue

Set-StrictMode -Version Latest

Add-Type -AssemblyName System.Windows.Forms

Add-Type -Language CSharp -TypeDefinition @'
using System.Runtime.InteropServices;
namespace Win32API {
  public class Shlwapi {
    [DllImport("Shlwapi.dll", EntryPoint = "StrCmpLogicalW", CharSet = CharSet.Unicode)]
    public static extern int StrCmpLogicalW(
      [MarshalAs(UnmanagedType.LPWStr)] string psz1, 
      [MarshalAs(UnmanagedType.LPWStr)] string psz2
    );
  }
}
'@

# フォームにドラッグ&ドロップされたファイルのパスを取得
$paths = Invoke-Command -ScriptBlock {
  $refPaths = [ref]$null

  $form = [Windows.Forms.Form]::new()
  $form.AllowDrop = $true
  $form.Text = "ドラッグ&ドロップ"
  $form.Width = 300
  $form.Height = 200

  # ドラッグ
  $form.add_DragEnter([System.Windows.Forms.DragEventHandler]{
    param([object]$sender, [Windows.Forms.DragEventArgs]$e)

    try {
      # ドラッグされた項目のパスを取得
      $paths = $e.Data.GetFileDropList()

      if ($paths.Count -eq 0) {
        return
      }

      # ファイル以外を含む場合はドロップ不可とする
      foreach ($path in $paths) {
        if ( -not [System.IO.File]::Exists($path) ) {
          return
        }
      }

      $e.Effect = [Windows.Forms.DragDropEffects]::Copy

      # ドラッグされた項目のパスを確認
      $paths.ForEach({ [PSCustomObject]@{ Event = "DragEnter"; Value = $_ } }) | 
      Format-Table -AutoSize -Wrap | Out-String -Width 80 | Write-Verbose
    }
    catch {
      Write-Error -ErrorRecord $_ -ErrorAction Continue
    }
  })

  # ドロップ
  $form.add_DragDrop([System.Windows.Forms.DragEventHandler]{
    param([object]$sender, [Windows.Forms.DragEventArgs]$e)

    try {
      # ドロップされたファイルのパスを取得
      $refPaths.Value = [string[]]$e.Data.GetFileDropList()

      # パスをソート
      [System.Array]::Sort($refPaths.Value, [System.Comparison[string]]{
        param ([string]$x, [string]$y)
        return [Win32API.Shlwapi]::StrCmpLogicalW($x, $y)
      })

      # ソート後のパスを確認
      $refPaths.Value.ForEach({ [PSCustomObject]@{ Event = "DragDrop"; Value=$_ } }) | 
      Format-Table -AutoSize -Wrap | Out-String -Width 80 | Write-Verbose
    }
    catch {
      Write-Error -ErrorRecord $_ -ErrorAction Continue
    }
  })

  $null = $form.ShowDialog()
  $form.Dispose()

  # unary array expression
  return (, $refPaths.Value)
}

$paths