pwsh/Get-Streams.ps1

using namespace System.Runtime.InteropServices

Set-Alias -Name streams -Value Get-Streams
function Get-Streams {
  <#
    .SYNOPSIS
        Search alternate data streams of files or folders.
    .DESCRIPTION
        `Get-Streams` just scans the (file|directory) you pass it for alternate data streams.
    .PARAMETER Path
        Specifies a (file|directory) location. Wildcards are accepted.
    .PARAMETER LiteralPath
        Specifies a (file|directory) location. The value of LiteralPath is used exactly as it
        is typed. No characters are interpreted as wildcards.
    .PARAMETER Delete
        Indicates the deletion of the found alternative stream(s).
    .INPUTS
        System.String
    .OUTPUTS
        System.Object[]
    .EXAMPLE
        Get-Streams .\regular.txt
 
        Name Size Allocation
        ---- ---- ----------
        :stream1:$DATA 14 16
        -------------
        Totally found: 1 stream(s).
    .EXAMPLE
        Get-Streams .\regular.txt -Delete
        Same that above but it also removes founded alternate stream.
    .EXAMPLE
        Get-Streams E:\folder
        Checks alternate streams of folder.
    .NOTES
        MIT
    .LINK
        None
  #>

  [CmdletBinding(DefaultParameterSetName='Path')]
  param(
    [Parameter(Mandatory,
               ParameterSetName='Path',
               Position=0,
               ValueFromPipeline,
               ValueFromPipelineByPropertyName)]
    [ValidateNotNullOrEmpty()]
    [SupportsWildcards()]
    [String]$Path,

    [Parameter(Mandatory,
               ParameterSetName='LiteralPath',
               Position=0,
               ValueFromPipelineByPropertyName)]
    [Alias('PSPath')]
    [String]$LiteralPath,

    [Parameter()][Switch]$Delete
  )

  begin {
    if ($PSCmdlet.ParameterSetName -eq 'Path') {
      $PipelineInput = !$PSBoundParameters.ContainsKey('Path')
    }

    function private:Find-Streams([Object]$Target) { # IO.(Directory|File)Info
      process {
        New-Delegate kernel32 {
          sfh CreateFileW([buf, int, IO.FileShare, ptr, IO.FileMode, int, ptr])
        }

        New-Delegate ntdll {
          int NtQueryInformationFile([sfh, buf, ptr, int, int])
        }

        if (($sfh = $kernel32.CreateFileW.Invoke(
          [buf].Uni($Target), 0x80000000, [IO.FileShare]::Read,
          [IntPtr]::Zero, [IO.FileMode]::Open, 0x02000000, [IntPtr]::Zero
        )).IsInvalid) {
          throw [InvalidOperationException]::new("File object is unavailable.")
        }

        $sz, $isb = 0x4000, [Byte[]]::new([IntPtr]::Size * 2) # IO_STATUS_BLOCK
        try {
          $ptr = [Marshal]::AllocHGlobal($sz)

          while ($ntdll.NtQueryInformationFile.Invoke($sfh, $isb, $ptr, $sz, 0x16) -ne 0) {
            $ptr = [Marshal]::ReAllocHGlobal($ptr, [IntPtr]($sz *= 2))
          }

          $tmp = $ptr
          for ($i = 0;;) {
            $fsi = Read-DataValues -Handle $tmp -Map I2 # getting StreamNameLength
            if ($fsi[1] -gt [UInt16]::MaxValue) { break } # directory
            $fsi = Read-DataValues -Handle $tmp -Map "I2l2B$($fsi[1] / 2)"

            if ($fsi[4] -ne '::$DATA') {
              [PSCustomObject]@{
                Name = $fsi[4]
                Size = $fsi[2]
                Allocation = $fsi[3]
              }

              if ($Delete) {
                Remove-Item "$(Get-Item $Target)$(
                  [Regex]::Match($fsi[4], '^:([^:]*)').Value
                )"
 -Force
              }
              ++$i # stream found
            }

            if ($fsi[0] -eq 0) { break }
            $tmp = [ptr].Mov($tmp, $fsi[0])
          }
        }
        catch { Write-Verbose $_ }
        finally { if ($ptr) { [Marshal]::FreeHGlobal($ptr) } }

        $sfh.Dispose()
        Write-Verbose "$($sfh.IsClosed)"
        "$('-' * 13)`n`e[35;1mTotally found`e[33;0m: $i stream(s)."
      }
    }
  }
  process {}
  end {
    .({Find-Streams (Get-Item -LiteralPath $LiteralPath)},{
      Find-Streams ((Get-Item $Path -ErrorAction 0), $Path)[$PipelineInput]
    })[$PSCmdlet.ParameterSetName -eq 'Path']
  }
}

Export-ModuleMember -Alias streams -Function Get-Streams