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 |