xlogI125’s blog

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

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