xlogI125’s blog

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

VBA: WshShell.Exec("PowerShell -Command -")

VBA: WshShell.Exec

Excelマクロから簡単にPowerShellの出力結果を取得する方法を考える

' Excel 2019, Windows 11 (2024年4月頃)

' 参照設定
' Windows Script Host Object Model

Option Explicit

Public Function f(ByVal cmd As String)
  Dim exitCode As Long

  Dim scOut As New VBA.Collection
  Dim scErr As New VBA.Collection

  Dim i As Long

  ' WshShell Object
  Dim wshShell As Object ' IWshRuntimeLibrary.wshShell
  Set wshShell = VBA.Interaction.CreateObject("WScript.Shell")

  ' WshScriptExec Object
  Dim wshScriptExec As Object ' IWshRuntimeLibrary.wshExec
  Set wshScriptExec = wshShell.Exec("PowerShell.exe -NoProfile -NonInteractive -WindowStyle Hidden -Command -")

  ' TextStream Object
  With wshScriptExec.StdIn
    .WriteLine cmd
    .Close
  End With

  ' TextStream Object
  With wshScriptExec.StdOut
    Do Until .AtEndOfStream
      scOut.Add .ReadLine()
    Loop
    .Close
  End With

  ' TextStream Object
  With wshScriptExec.StdErr
    Do Until .AtEndOfStream
      scErr.Add .ReadLine()
    Loop
    .Close
  End With

  Set wshScriptExec = Nothing

  Set wshShell = Nothing

  For i = 1 To scOut.Count Step 1
    Debug.Print scOut.Item(i)
  Next i

  For i = 1 To scErr.Count Step 1
    Debug.Print scErr.Item(i)
  Next i

  f = exitCode
End Function

Public Sub main()
  Debug.Print f( _
    "Set-StrictMode -Version Latest" & VBA.Constants.vbCrLf & _
    "Set-Location -LiteralPath ""${env:USERPROFILE}\Desktop""" & VBA.Constants.vbCrLf & _
    "(Get-ChildItem).Name" & VBA.Constants.vbCrLf & _
    "Write-Error ""Errorテスト""" & VBA.Constants.vbCrLf & _
    "exit" _
  )
End Sub

PowerShell: WshShell.Exec

COMオブジェクトの解放は気にしない

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

Set-StrictMode -Version Latest

$sb = {
  param([Parameter(Mandatory)][ValidateNotNullOrEmpty()][string]$cmd)

  # StringCollection
  $scOut = [System.Collections.Specialized.StringCollection]::new()
  $scErr = [System.Collections.Specialized.StringCollection]::new()

  # WshShell Object
  $wshShell = [System.Activator]::CreateInstance([System.Type]::GetTypeFromProgID("WScript.Shell"))

  # WshScriptExec Object
  $wshScriptExec = $wshShell.Exec("PowerShell.exe -NoProfile -NonInteractive -WindowStyle Normal -Command -")

  # TextStream Object
  $textStreamIn = $wshScriptExec.StdIn
  $textStreamIn.WriteLine($cmd)
  $textStreamIn.Close()
  $textStreamIn = $null

  # TextStream Object
  $textStreamOut = $wshScriptExec.StdOut
  while (-not $textStreamOut.AtEndOfStream) { $null = $scOut.Add($textStreamOut.ReadLine()) }
  $textStreamOut.Close()
  $textStreamOut = $null

  # TextStream Object
  $textStreamErr = $wshScriptExec.StdErr
  while (-not $textStreamErr.AtEndOfStream) { $null = $scErr.Add($textStreamErr.ReadLine()) }
  $textStreamErr.Close()
  $textStreamErr = $null

  $exitCode = $wshScriptExec.ExitCode

  $wshScriptExec = $null

  $wshShell = $null

  $scOut | Write-Host -ForegroundColor Cyan
  $scErr | Write-Host -ForegroundColor Magenta

  return $exitCode
}

Invoke-Command -ScriptBlock $sb -ArgumentList @'
Set-StrictMode -Version Latest
Set-Location -LiteralPath "${env:USERPROFILE}\Desktop"
(Get-ChildItem).Name
Write-Error "Errorテスト"
exit
'@

PowerShell: ProcessStartInfo

Start-Processコマンドレットの-WaitパラメーターではなくProcess.WaitForExitメソッドを使いたい場合があるのでProcessStartInfoクラスを使う

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

Set-StrictMode -Version Latest

$sb = {
  param([Parameter(Mandatory)][ValidateNotNullOrEmpty()][string]$cmd)

  $scOut = [System.Collections.Specialized.StringCollection]::new()
  $scErr = [System.Collections.Specialized.StringCollection]::new()

  # Process
  $p = [System.Diagnostics.Process]::new()

  # ProcessStartInfo
  $p.StartInfo.FileName = "PowerShell.exe"
  $p.StartInfo.Arguments = "-NoProfile -NonInteractive -WindowStyle Normal -Command -"
  $p.StartInfo.UseShellExecute = $false
  $p.StartInfo.RedirectStandardInput = $true
  $p.StartInfo.RedirectStandardOutput = $true
  $p.StartInfo.RedirectStandardError = $true

  $null = $p.Start()

  $p.StandardInput.WriteLine($cmd)
  $p.StandardInput.Close()

  $p.WaitForExit()

  # StreamReader
  while (-not $p.StandardOutput.EndOfStream) {
    $null = $scOut.Add($p.StandardOutput.ReadLine())
  }
  $p.StandardOutput.Close()

  # StreamReader
  while (-not $p.StandardError.EndOfStream) {
    $null = $scErr.Add($p.StandardError.ReadLine())
  }
  $p.StandardError.Close()

  $exitCode = $p.ExitCode

  $p.Dispose()

  $scOut | Write-Host -ForegroundColor Cyan
  $scErr | Write-Host -ForegroundColor Magenta

  return $exitCode
}

Invoke-Command -ScriptBlock $sb -ArgumentList @'
Set-StrictMode -Version Latest
Set-Location -LiteralPath "${env:USERPROFILE}\Desktop"
Get-ChildItem | Format-Table -AutoSize | Out-String -Width 80 | Write-Host
Write-Error "Errorテスト"
exit
'@

Webブラウザ JavaScript ハッシュ値

crypto.subtle.digestでファイルのハッシュ値を求める。
PowerShellGet-FileHashコマンドレットではなく、Webブラウザでファイルのハッシュ値を求める方法を考える。

filesプロパティ(<input type="file">)

選択されたファイルのハッシュ値を出力

// <!doctype html>
// <html><body><script>
"use strict";

// テスト環境: Edge, Firefox, Windows 11 (2024年4月頃)

// スタイルシート
let sheet = new CSSStyleSheet();
sheet.insertRule("table { border-collapse: collapse; }");
sheet.insertRule("th, td { border: 1px solid black; padding: 0.5em; }");
document.adoptedStyleSheets = [sheet];

// input 要素
let elmInputFile = document.createElement("input");
elmInputFile.type = "file";
elmInputFile.multiple = true;
document.body.appendChild(elmInputFile);

document.body.appendChild(document.createElement("br"));

// button 要素
let elmButton = document.createElement("button");
elmButton.innerText = "クリック";
document.body.appendChild(elmButton);

document.body.appendChild(document.createElement("br"));

// ボタン click
elmButton.onclick = function() {
  // table 要素
  let elmTable = document.createElement("table");
  document.body.appendChild(elmTable);
  let elmTbody = document.createElement("tbody");
  elmTable.appendChild(elmTbody);
  elmTbody.insertAdjacentHTML("beforeend", "<tr><th>Name</th><th>Hash</th></tr>") 

  // crypto.subtle.digest
  for (const file of elmInputFile.files) {
    file.arrayBuffer().then(arrBuf => {
      crypto.subtle.digest("SHA-256", arrBuf).then(arrBuf => {
        const strHash = Array.from(new Uint8Array(arrBuf)).map(byte => byte.toString(16).padStart(2, "0")).join("").toUpperCase();
        elmTbody.insertAdjacentHTML("beforeend", `<tr><td>${file.name}</td><td>${strHash}</th></td>`) 
      });
    });
  }

  document.body.appendChild(document.createElement("br"));
};
// </script></body></html>

dataTransfer.files

Fileオブジェクトのnameプロパティなどを表示

// <!doctype html>
// <html><body><script>
"use strict";

// テスト環境: Edge, Firefox, Windows 11 (2024年4月頃)

let elmTextarea = document.createElement("textarea");
document.body.appendChild(elmTextarea);

elmTextarea.textContent = "ここにファイルをドラッグ&ドロップ";

// dragover イベント
elmTextarea.addEventListener("dragover", function(event) {
  event.preventDefault();
  console.log(event);
  event.target.textContent = "dragover イベント";
});

// drop イベント
elmTextarea.addEventListener("drop", function(event) {
  event.preventDefault();
  console.log(event);
  event.target.textContent = "";

  // dataTransfer プロパティ
  for (const file of event.dataTransfer.files) {
    event.target.textContent += JSON.stringify({ name: file.name, type: file.type, size: file.size }) + "\n";
  }

});
// </script></body></html>

参考: Get-FileHashコマンドレット

フォームにドラッグされたファイルのハッシュ値をテキストボックスに表示

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

$ErrorActionPreference = "Stop"
$VerbosePreference = "Continue"

Set-StrictMode -Version Latest

Add-Type -Language CSharp -Namespace Win32API -Name Shlwapi -MemberDefinition @'
  [DllImport("Shlwapi.dll", EntryPoint = "StrCmpLogicalW", CharSet = CharSet.Unicode)]
  public static extern int StrCmpLogicalW([MarshalAs(UnmanagedType.LPWStr)] string psz1, [MarshalAs(UnmanagedType.LPWStr)] string psz2);
'@

Add-Type -AssemblyName System.Windows.Forms

# フォーム
$form = [Windows.Forms.Form]::new()
$form.Text = "ファイルをドラッグ&ドロップ"
$form.Size = [System.Drawing.Size]::new(800, 600)
$form.FormBorderStyle = [System.Windows.Forms.FormBorderStyle]::FixedDialog
$form.AllowDrop = $true

# テキストボックス
$textBox = [System.Windows.Forms.TextBox]::new()
$textBox.Size = [System.Drawing.Size]::new(
  800 - [System.Windows.Forms.SystemInformation]::VerticalScrollBarWidth, 
  600 - [System.Windows.Forms.SystemInformation]::HorizontalScrollBarHeight - [System.Windows.Forms.SystemInformation]::CaptionHeight
)
$textBox.AutoSize = $false
$textBox.Font = [System.Drawing.Font]::new("MS ゴシック", 12)
$textBox.Multiline = $true
$textBox.AcceptsReturn = $true
$textBox.WordWrap = $false
$textBox.ScrollBars = [System.Windows.Forms.ScrollBars]::Both
$textBox.ShortcutsEnabled = $true

$form.Controls.Add($textBox)

# DragEnterイベント
$form.add_DragEnter({
  param([object]$sender, [System.Windows.Forms.DragEventArgs]$e)
  try {
    $e.Effect = [System.Windows.Forms.DragDropEffects]::Copy
  }
  catch {
    $_ | Write-Verbose
  }
} -as [System.Windows.Forms.DragEventHandler])

# DragDropイベント
$form.add_DragDrop({
  param([object]$sender, [System.Windows.Forms.DragEventArgs]$e)
  try {
    # ファイルのパス
    $paths = [string[]]$e.Data.GetFileDropList()
    # パスをソート
    [System.Array]::Sort($paths, { param([string]$x, [string]$y); return [Win32API.Shlwapi]::StrCmpLogicalW($x, $y) } -as [System.Comparison[string]])
    # ファイルのハッシュ値
    $fileHashs = Get-FileHash -LiteralPath $paths

    foreach ($fileHash in $fileHashs) {
      # 空白行を削除
      $strs = ($fileHash | Format-List -Property @{ Name = "Name"; Expression = { [System.IO.Path]::GetFileName($_.Path) } }, Hash, Algorithm | Out-String -Width 80 -Stream) -ne ""
      $str = $strs -join "`r`n"
      # ファイルのハッシュ値をテキストボックスに表示
      $textBox.Text += $str + "`r`n`r`n"
    }

    # テキストボックスをスクロール
    $textBox.Select($textBox.Text.Length, 0)
    $textBox.ScrollToCaret()
  }
  catch {
    $_ | Write-Verbose
  }
} -as [System.Windows.Forms.DragEventHandler])

$form.ShowDialog()

Acrobat Reader: 長方形の対角線位置に直線を追加

バツ印に見える注釈を作成したいので、長方形の対角線位置に直線を追加する方法を考える。

// テスト環境: Acrobat Reader (2024年3月頃), Windows 11
// ファイル名: %APPDATA%\Adobe\Acrobat\Privileged\DC\JavaScripts\test.js
// エンコード: ANSI

// 起動時に毎回JavaScriptデバッガーを表示
console.show();
console.println("// コンソールでの実行はテキストを範囲選択して Ctrl + Enter");
console.println("// app.getPath('user', 'javascript');");
// #=> /C/Users/xxx/AppData/Roaming/Adobe/Acrobat/Privileged/DC/JavaScripts

// メニューに登録
app.addSubMenu({ cParent: "Help", nPos: 0, cName: "MyTest", cUser: "テスト" });
app.addSubMenu({ cParent: "Help", nPos: 1, cName: "-",      cUser: ""       });

app.addMenuItem({
  cParent: "MyTest", 
  cName  : "MyTestShowConsole", 
  cUser  : "コンソールを表示", 
  cEnable: "event.rc = true;", 
  cExec  : "(() => { console.show(); })();"
});

app.addMenuItem({
  cParent: "MyTest", 
  cName  : "-", 
  cExec  : ""
});

app.addMenuItem({
  cParent: "MyTest", 
  cName  : "MyTestShowAnnotProps", 
  cUser  : "注釈のプロパティを表示", 
  cEnable: "event.rc = (event.target != null);", 
  cExec  : util.printf("(%s)();", (() => {
    console.clear();
    const annots = this.selectedAnnots;
    if (annots === undefined) return;
    var str = "";
    annots.forEach((annot, i) => {
      const prop = annot.getProps();
      for (name in prop) {
        switch (name) {
          case "points":
            var x1mm = prop[name][0][0] * 25.4 / 72;
            var y1mm = prop[name][0][1] * 25.4 / 72;
            var x2mm = prop[name][1][0] * 25.4 / 72;
            var y2mm = prop[name][1][1] * 25.4 / 72;
            str += util.printf("%02d %s : [[%.1f, %.1f], [%.1f, %.1f]] // mm\r", i, name, x1mm, y1mm, x2mm, y2mm);
            break;
          case "popupRect":
          case "rect":
            if (prop[name] !== undefined) {
              var x1mm = prop[name][0] * 25.4 / 72;
              var y1mm = prop[name][1] * 25.4 / 72;
              var x2mm = prop[name][2] * 25.4 / 72;
              var y2mm = prop[name][3] * 25.4 / 72;
              str += util.printf("%02d %s : [%.1f, %.1f, %.1f, %.1f] // mm\r", i, name, x1mm, y1mm, x2mm, y2mm);
              break;
            }
          default:
            str += util.printf("%02d %s : %s\r", i, name, prop[name]);
        }
      }
    });
    console.println(str);
    console.show();
  }).toString())
});

app.addMenuItem({
  cParent: "MyTest", 
  cName  : "-", 
  cExec  : ""
});

app.addMenuItem({
  cParent: "MyTest", 
  cName  : "MyTestAddLine", 
  cUser  : "長方形の対角線位置に直線を追加", 
  cEnable: "event.rc = (event.target != null);", 
  cExec  : util.printf("(%s)();", (() => {
    // 選択された注釈を取得
    const annots = this.selectedAnnots;
    if (annots === undefined) return;
    annots.forEach(annot => {
      switch (annot.type) {
        case "Square"  : break; // 長方形
        case "FreeText": break; // テキストボックス
        default        : return;
      }
      // 形状が長方形である注釈のプロパティを取得
      const propRect = annot.getProps();
      // 線のプロパティを設定
      var propLine = {
        type        : "Line", 
        page        : propRect.page, 
        points      : [[0, 0], [1, 1]], 
        doCaption   : false, 
        leaderExtend: 0, 
        leaderLength: 0, 
        // 表示方法
        arrowBegin : "None", 
        arrowEnd   : "None", 
        style      : "S", 
        width      : propRect.width, 
        strokeColor: propRect.strokeColor, 
        fillColor  : propRect.fillColor, 
        opacity    : propRect.opacity, 
        // 一般
        author : propRect.author, 
        subject: propRect.subject, 
        modDate: propRect.modDate, 
        // ロックなど
        lock: false, readOnly: false, hidden: false, noView: false, toggleNoView: false, print: true, delay: false
      };
      // 線を追加
      const annotLine1 = this.addAnnot(propLine);
      const annotLine2 = this.addAnnot(propLine);
      // 線の座標を変更
      const [x1, y1, x2, y2] = propRect.rect;
      annotLine1.points = [[x1, y1], [x2, y2]];
      annotLine2.points = [[x1, y2], [x2, y1]];
    });
  }).toString())
});

PowerShell練習 Select-ObjectのPropertyパラメーター 2

Select-Objectでプロパティを指定してから表示

# PowerShell 5.1, Windows 11 (2024年2月頃)
Set-StrictMode -Version Latest

Add-Type -Language CSharp -Namespace Win32API -Name Shlwapi -MemberDefinition @'
  [DllImport("Shlwapi.dll", EntryPoint = "StrCmpLogicalW", CharSet = CharSet.Unicode)]
  public static extern int StrCmpLogicalW([MarshalAs(UnmanagedType.LPWStr)] string psz1, [MarshalAs(UnmanagedType.LPWStr)] string psz2);
'@

# 配列
$psObjs = [PSObject[]]@(
  [PSCustomObject]@{ Name="05 file.txt" ; A="ValA" }
  [PSCustomObject]@{ Name="050 file.txt"; B="ValB" }
  [PSCustomObject]@{ Name="1 file.txt"  ; C="ValC" }
  [PSCustomObject]@{ Name="10 file.txt" ; D="ValD" }
  [PSCustomObject]@{ Name="100 file.txt" }
)

# 配列をソート
[System.Array]::Sort($psObjs, { [Win32API.Shlwapi]::StrCmpLogicalW($args[0].Name, $args[1].Name) } -as [System.Comparison[PSObject]] )

# プロパティ名
$propNames = $psObjs.ForEach({ $_.PSObject.Properties.ForEach({ $_.Name }) }) | Sort-Object | Get-Unique

$propNames = @("Name") + @($propNames -ne "Name")

# 表示
$psObjs | ConvertTo-Csv -NoTypeInformation | ConvertFrom-Csv | Format-Table -AutoSize | Out-String | Write-Host

# Name         C
# ----         -
# 1 file.txt   ValC
# 05 file.txt
# 10 file.txt
# 050 file.txt
# 100 file.txt

# Select-Objectでプロパティを指定してから表示
$psObjs | Select-Object -Property $propNames | ConvertTo-Csv -NoTypeInformation | ConvertFrom-Csv | Format-Table -AutoSize | Out-String | Write-Host

# Name         A    B    C    D
# ----         -    -    -    -
# 1 file.txt             ValC
# 05 file.txt  ValA
# 10 file.txt                 ValD
# 050 file.txt      ValB
# 100 file.txt
# PowerShell 5.1, Windows 11 (2024年2月頃)
Set-StrictMode -Version Latest

Add-Type -Language CSharp -Namespace Win32Functions -Name Shlwapi -MemberDefinition @'
  [DllImport("Shlwapi.dll", EntryPoint = "StrCmpLogicalW", CharSet = CharSet.Unicode)]
  public static extern int StrCmpLogicalW([MarshalAs(UnmanagedType.LPWStr)] string psz1, [MarshalAs(UnmanagedType.LPWStr)] string psz2);
'@

# 並べ替え後のFileSystemInfoの配列を取得
$fsis = & {
  param ([HashTable]$Splatting)

  # Get-ChildItemにてFileInfoとDirectoryInfoを取得
  $fsis = Get-ChildItem @Splatting

  # AddRangeを使うため
  $fsiList = [System.Collections.Generic.List[System.IO.FileSystemInfo]]::new()

  # Comparison
  $cmpName = { param ($x, $y); return [Win32Functions.Shlwapi]::StrCmpLogicalW($x.Name, $y.Name) }
  $comparisonGroupInfo     = [System.Comparison[Microsoft.PowerShell.Commands.GroupInfo]]$cmpName
  $comparisonDirectoryInfo = [System.Comparison[System.IO.DirectoryInfo]]$cmpName
  $comparisonFileInfo      = [System.Comparison[System.IO.FileInfo]]$cmpName

  # Group-ObjectにてPSParentPathでグループ化
  $grs = [Microsoft.PowerShell.Commands.GroupInfo[]]@($fsis | Group-Object -Property PSParentPath)

  # GroupInfoの配列をArray.Sortで並べ替え
  [System.Array]::Sort($grs, $comparisonGroupInfo)

  for ($i = 0; $i -lt $grs.Length; $i++) {
    $fsisGr = [System.IO.FileSystemInfo[]]@($grs[$i].Group)

    # フォルダ
    $disGr = [System.IO.DirectoryInfo[]]@($fsisGr.Where({ [System.IO.Directory]::Exists($_.FullName) }))
    [System.Array]::Sort($disGr, $comparisonDirectoryInfo)
    $fsiList.AddRange($disGr)

    # ファイル
    $fisGr = [System.IO.FileInfo[]]@($fsisGr.Where({ [System.IO.File]::Exists($_.FullName) }))
    [System.Array]::Sort($fisGr, $comparisonFileInfo)
    $fsiList.AddRange($fisGr)
  }

  if ($fsiList.Count -ne $fsis.Length) {
    throw
  }

  return (, [System.IO.FileSystemInfo[]]$fsiList)
} -Splatting @{ LiteralPath="$env:USERPROFILE"; Force=$true; Recurse=$true; Depth=3 }

# 並べ替え後のプロパティ名を取得
$propertyNames = & {
  param ([PSObject[]]$PSObjects)

  # UnionWithを使うため
  $propNameSet = [System.Collections.Generic.HashSet[string]]::new()

  # 各要素のプロパティ名をHashSetに追加
  ($PSObjects -ne $null).ForEach({ $propNameSet.UnionWith([string[]]$_.PSObject.Properties.ForEach({ $_.Name })) })

  # 配列に変換
  $propNames = [string[]]$propNameSet

  # 配列の並べ替え
  $cmp = [System.Comparison[string]]{ param ($x, $y); return [Win32Functions.Shlwapi]::StrCmpLogicalW($x, $y) }
  [System.Array]::Sort($propNames, $cmp)

  return (, $propNames)
} -PSObjects $fsis

# スプラッティング
$splatting = Invoke-Command -NoNewScope -ScriptBlock $([ScriptBlock]::Create(@'
@{
  Property = @(
    @{ Name="_ParentPath"; Expr={ Convert-Path -LiteralPath $_.PSParentPath } }, 
    @{ Name="_Name"      ; Expr={ $_.Name } }
  ) + $propertyNames
  ExcludeProperty = @("Target", "VersionInfo")
}
'@))

$fsis | Select-Object @splatting | ConvertTo-Csv -NoTypeInformation | Set-Clipboard


dy100ms.hatenadiary.jp

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

フォームでのドロップ操作を省略し、ドラッグ操作とクリック操作を考える。
普通にショートカット(*.lnk)アイコンへのドラッグ&ドロップのほうが便利な気がする。

# [テスト環境]
#  PowerShell 5.1, Windows 11 (2024年2月頃)
# [スクリプトファイル(.ps1)]
#  <ファイル名>
#   "%USERPROFILE%\Desktop\test.ps1"
#  <エンコード>
#   UTF-8 (BOM付き)
# [ショートカット(.lnk)]
#  <リンク先(T)>
#   PowerShell.exe -NoLogo -NoExit -NoProfile -ExecutionPolicy RemoteSigned -File "%USERPROFILE%\Desktop\test.ps1"
#  <作業フォルダー(S)>
#   ""

$ErrorActionPreference = "Stop"
$VerbosePreference = "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);
  }
}
'@

# Comparison
$cmp = [System.Comparison[string]]{
  param ([string]$x, [string]$y)
  return [Win32API.Shlwapi]::StrCmpLogicalW($x, $y)
}

$paths = [string[]]$args
[System.Array]::Sort($paths, $cmp)
$paths.ForEach({ [PSCustomObject]@{Type="lnk";Value=$_} }) | Out-String | Write-Verbose

$form = [System.Windows.Forms.Form]::new()
$form.AllowDrop = $true

$form.add_DragEnter({
  try {
    # フォルダやテキストなどのドラッグ操作も受け付けるけど気にしない
    $_.Effect = [System.Windows.Forms.DragDropEffects]::Copy
    $list = [System.Collections.Generic.List[string]][string[]]$_.Data.GetFileDropList()
    $list.Sort($cmp)
    $list | Select-Object -Property @{Name="Type";Expr={"drag"}}, @{Name="Value";Expr={$_}} | Out-String | Write-Verbose
    $script:paths = [string[]]$list
  }
  catch {
    Write-Error -ErrorRecord $_ -ErrorAction Continue
  }
})

$form.add_Click({
  $this.Close()
})

[void]$form.ShowDialog()
$form.Dispose()

$paths

dy100ms.hatenadiary.jp

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

メモ: ラプラス変換

正しい内容は教科書などを参照してください

ラプラス変換

t<0 のとき y(t)=0 とする


\Large
\begin{align*}
Y(s)
&= \mathcal{L}\left[ y(t) \right] \\
&= \int_0^\infty y(t) \mathrm{e}^{-st} \mathrm{d}t
\end{align*}

変数 s の関数として見る

\Large
\begin{align*}
Y(s-a)
&= \int_0^\infty y(t) \mathrm{e}^{-\left(s - a\right)t} \mathrm{d}t \\
&= \int_0^\infty \left( y(t) \mathrm{e}^{at}     \right) \mathrm{e}^{-st} \mathrm{d}t \\
&= \mathcal{L}\left[ \mathrm{e}^{at} y(t) \right]
\end{align*}

微分された関数のラプラス変換


\Large
\begin{align*}
\mathcal{L}\left[ \frac{\mathrm{d}y(t)}{\mathrm{d}t} \right]
&= s \mathcal{L}\left[ y(t) \right] - y(0) \\
&= s Y(s) - y(0)
\end{align*}

y(t)=1 のとき

\Large
\begin{gather*}
\mathcal{L}\left[ \frac{\mathrm{d}1}{\mathrm{d}t} \right] = s \mathcal{L}\left[ 1 \right] - 1 \\
\mathcal{L}\left[ 1 \right] = \frac{1}{s}
\end{gather*}

Y(s-a) = \mathcal{L}\left[ \mathrm{e}^{at} y(t) \right] より

\Large
\begin{gather*}
\frac{1}{s - a} = \mathcal{L}\left[ \mathrm{e}^{at} 1 \right] \\
\mathcal{L}\left[ \mathrm{e}^{at} \right] = \frac{1}{s - a}
\end{gather*}

機械的a = \mathrm{j}\omega とする

\Large
\begin{gather*}
\mathcal{L}\left[ \mathrm{e}^{\mathrm{j} \omega t} \right]
= \frac{1}{s - \mathrm{j}\omega}
= \frac{s}{s^2 + \omega^2} + \mathrm{j}\frac{\omega}{s^2 + \omega^2}
\end{gather*}

Y(s-a) = \mathcal{L}\left[ \mathrm{e}^{at} y(t) \right] より

\Large
\begin{gather*}
\frac{s-a}{\left(s-a\right)^2 + \omega^2} + \mathrm{j}\frac{\omega}{\left(s-a\right)^2 + \omega^2}
= \mathcal{L}\left[ \mathrm{e}^{at} \mathrm{e}^{\mathrm{j} \omega t} \right]
\end{gather*}

導出過程は気にしない

\Large
\begin{gather*}
\mathcal{L}\left[ \mathrm{e}^{at} \cos \omega t \right] = \frac{s-a}{\left(s-a\right)^2 + \omega^2} \\
\mathcal{L}\left[ \mathrm{e}^{at} \sin \omega t \right] = \frac{\omega}{\left(s-a\right)^2 + \omega^2}
\end{gather*}

y(t)=t^n のとき

\Large
\begin{gather*}
\mathcal{L}\left[ \frac{\mathrm{d}t^n}{\mathrm{d}t} \right] = s \mathcal{L}\left[ t^n \right] - 0 \\
\mathcal{L}\left[ t^n \right] = \frac{n}{s} \mathcal{L}\left[ t^{n-1} \right]
\end{gather*}

n=1 のとき

\Large
\begin{gather*}
\mathcal{L}\left[ t \right]
= \frac{1}{s} \mathcal{L}\left[ 1 \right]
= \frac{1}{s^2}
\end{gather*}

n=2 のとき

\Large
\begin{gather*}
\mathcal{L}\left[ t^2 \right]
= \frac{2}{s} \mathcal{L}\left[ t \right]
= \frac{2}{s^3}
\end{gather*}

積分された関数のラプラス変換

t<0 のとき f(t)=0, g(t)=0 とするので \tau > t のとき g(t-\tau) = 0

\Large
\begin{align*}
\int_0^t f(\tau)g(t-\tau)\mathrm{d}\tau
&= \int_0^\infty f(\tau)g(t-\tau)\mathrm{d}\tau - \int_t^\infty f(\tau)g(t-\tau)\mathrm{d}\tau \\
&= \int_0^\infty f(\tau)g(t-\tau)\mathrm{d}\tau - 0 \\
&= \int_0^\infty f(\tau)g(t-\tau)\mathrm{d}\tau
\end{align*}


\Large
\begin{align*}
\int_{-\tau_0}^\infty f(\tau)g(t-\tau)\mathrm{d}\tau
&= \int_{-\tau_0}^0 f(\tau)g(t-\tau)\mathrm{d}\tau + \int_0^\infty f(\tau)g(t-\tau)\mathrm{d}\tau \\
&= 0 + \int_0^\infty f(\tau)g(t-\tau)\mathrm{d}\tau \\
&= \int_0^\infty f(\tau)g(t-\tau)\mathrm{d}\tau
\end{align*}


\Large
\begin{align*}
\mathcal{L}\left[ \int_0^t f(\tau)g(t - \tau)\mathrm{d}\tau \right]
&= \int_0^\infty \left( \int_0^t      f(\tau)g(t-\tau)\mathrm{d}\tau \right) \mathrm{e}^{-st} \mathrm{d}t \\
&= \int_0^\infty \left( \int_0^x      f(y)g(x-y)\mathrm{d}y \right) \mathrm{e}^{-sx} \mathrm{d}x \\
&= \int_0^\infty \left( \int_0^\infty f(y)g(x-y)\mathrm{d}y \right) \mathrm{e}^{-sx} \mathrm{d}x \\
&= \int_0^\infty \left( \int_0^\infty f(y)g(x-y) \mathrm{e}^{-sx} \mathrm{d}y \right) \mathrm{d}x \\
&= \int_0^\infty \left( \int_0^\infty f(y) \mathrm{e}^{-sy} g(x-y) \mathrm{e}^{-s\left(x-y\right)} \mathrm{d}y \right) \mathrm{d}x \\
&= \int_0^\infty \left( \int_0^\infty f(y) \mathrm{e}^{-sy} g(x-y) \mathrm{e}^{-s\left(x-y\right)} \mathrm{d}x \right) \mathrm{d}y \\
&= \int_0^\infty f(y) \mathrm{e}^{-sy} \left( \int_0^\infty g(x-y) \mathrm{e}^{-s\left(x-y\right)} \mathrm{d}x \right) \mathrm{d}y \\
&= \int_0^\infty f(y) \mathrm{e}^{-sy} \left( \int_0^\infty g(\tau) \mathrm{e}^{-s\tau} \mathrm{d}\tau \right) \mathrm{d}y \\
&= \left( \int_0^\infty f(y) \mathrm{e}^{-sy} \mathrm{d}y \right) \left( \int_0^\infty g(\tau) \mathrm{e}^{-s\tau} \mathrm{d}\tau \right) \\
&= \left( \int_0^\infty f(t) \mathrm{e}^{-st} \mathrm{d}t \right) \left( \int_0^\infty g(t) \mathrm{e}^{-st} \mathrm{d}t \right) \\
&= \mathcal{L}\left[ f(t) \right] \mathcal{L}\left[ g(t) \right] \\
&= F(s)G(s)
\end{align*}

g(t)=1 のとき

\Large
\begin{gather*}
\mathcal{L}\left[ \int_0^t f(\tau) \mathrm{d}\tau \right] = F(s) \frac{1}{s}
\end{gather*}


\Large
\begin{align*}
\mathcal{L}\left[ \int_0^t y(t) \mathrm{d}t \right]
&= \frac{1}{s} \mathcal{L}\left[ y(t) \right] \\
&= \frac{1}{s} Y(s)
\end{align*}

終値


\Large
\begin{gather*}
\left\{
\begin{aligned}
&\lim_{s \to 0} \int_0^\infty \left( \frac{\mathrm{d}y(t)}{\mathrm{d}t} \right) \mathrm{e}^{-st} \mathrm{d}t &{}
&= \lim_{s \to 0} \mathcal{L}\left[ \frac{\mathrm{d}y(t)}{\mathrm{d}t} \right] &{}
&= \lim_{s \to 0} \left( s \mathcal{L}\left[ y(t) \right] - y(0) \right) \\
%
&\lim_{s \to 0} \int_0^\infty \left( \frac{\mathrm{d}y(t)}{\mathrm{d}t} \right) \mathrm{e}^{-st} \mathrm{d}t &{}
&= \int_0^\infty \left( \frac{\mathrm{d}y(t)}{\mathrm{d}t} \right) \mathrm{d}t &{}
&= y(\infty) - y(0)
\end{aligned}
\right.
\end{gather*}


\Large
\begin{gather*}
y(\infty) = \lim_{s \to 0} s \mathcal{L}\left[ y(t) \right] = \lim_{s \to 0} s Y(s) \\
\end{gather*}

初期値


\Large
\begin{gather*}
\left\{
\begin{aligned}
&\lim_{s \to \infty} \int_0^\infty \left( \frac{\mathrm{d}y(t)}{\mathrm{d}t} \right) \mathrm{e}^{-st} \mathrm{d}t &{}
&= \lim_{s \to \infty} \mathcal{L}\left[ \frac{\mathrm{d}y(t)}{\mathrm{d}t} \right] &{}
&= \lim_{s \to \infty} \left( s \mathcal{L}\left[ y(t) \right] - y(0) \right) \\
%
&\lim_{s \to \infty} \int_0^\infty \left( \frac{\mathrm{d}y(t)}{\mathrm{d}t} \right) \mathrm{e}^{-st} \mathrm{d}t &{}
&= \int_0^\infty 0 \mathrm{d}t &{}
&= 0
\end{aligned}
\right.
\end{gather*}


\Large
\begin{gather*}
y(0) = \lim_{s \to \infty} s \mathcal{L}\left[ y(t) \right] = \lim_{s \to \infty} s Y(s) \\
\end{gather*}