usr/Get-Streams.ps1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
using namespace System.Runtime.InteropServices

Set-Alias -Name streams -Value Get-Streams
function Get-Streams {
  [CmdletBinding(DefaultParameterSetName='Path')]
  param(
    [Parameter(Mandatory,
               ParameterSetName='Path',
               Position=0,
               ValueFromPipeline,
               ValueFromPipelineByPropertyName)]
    [ValidateNotNullOrEmpty()]
    [SupportsWildcards()]
    [String]$Path,

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

    [Parameter()][Switch]$Delete
  )

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

    function private:Find-Streams([Object]$Target) {
      process {
        New-Structure FILE_STREAM_INFORMATION {
          UInt32 NextEntryOffset
          UInt32 StreamNameLength
          Int64  StreamSize
          Int64  StreamAllocationSize
          String 'StreamName ByValTStr 1'
        } -CharSet Unicode

        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('Unavailable file system object.')
        }

        $sz, $isb = 0x400, [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."ToInt$([IntPtr]::Size * 8)"()
          for ($i = 0;;) {
            # prevent CLR (op_Implicit) exception
            if ([Marshal]::ReadInt32([IntPtr]$tmp) -lt 0) {break}
            $fsi = ([IntPtr]$tmp) -as [FILE_STREAM_INFORMATION]
            if (($name = [Marshal]::PtrToStringUni(
              [IntPtr]($tmp + $fsi::OfsOf('StreamName')), $fsi.StreamNameLength / 2
            )) -ne '::$DATA') {
              [PSCustomObject]@{
                Name = $name
                Size = $fsi.StreamSize
                Allocation = $fsi.StreamAllocationSize
              }

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

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

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

Export-ModuleMember -Alias streams -Function Get-Streams