strings.psm1

#requires -version 5
using namespace System.IO
using namespace System.Text

Set-Alias -Name strings -Value Get-Strings
function Get-Strings {
  <#
    .SYNOPSIS
        Search strings in binary files.
    .DESCRIPTION
        `Get-Strings` just scans the file you pass it for Unicode or ASCII strings of a
        default length of 3 characters.
    .PARAMETER Path
        Specifies a file location. Wildcards are accepted.
    .PARAMETER LiteralPath
        Specifies a file location. The value of LiteralPath is used exactly as it is
        typed. No characters are interpreted as wildcards.
    .PARAMETER BytesToProcess
        Bytes of file to scan.
    .PARAMETER BytesOffset
        File offset at which to start scanning.
    .PARAMETER StringLength
        Minimum string length (default is 3).
    .PARAMETER StringOffset
        Print offset if file string was located.
    .PARAMETER Unicode
        Unicode-only search.
    .INPUTS
        System.String
    .OUTPUTS
        System.Object[]
    .EXAMPLE
        Get-Item .\file.bin | Get-Strings
        Process entire file, locate both ASCII and Unicode strings with default settings.
    .EXAMPLE
        Get-Strings .\file.bin -b 20 -f 100 -o
        Skip 100 bytes and process next 20 bytes of a file, show founded string offset.
    .EXAMPLE
        Get-Strings .\file.bin -u
        Process entire file but search only Unicode strings.
    .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)]
    [ValidateNotNullOrEmpty()]
    [Alias('PSPath')]
    [String]$LiteralPath,

    [Parameter()][Alias('b')][UInt32]$BytesToProcess = 0,
    [Parameter()][Alias('f')][UInt32]$BytesOffset    = 0,
    [Parameter()][Alias('n')][Byte]  $StringLength   = 3,
    [Parameter()][Alias('o')][Switch]$StringOffset,
    [Parameter()][Alias('u')][Switch]$Unicode
  )

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

    function private:Find-Strings([FileInfo]$File) {
      process {
        try {
          $fs = [File]::OpenRead($File.FullName)
          # unable to read beyond file length
          if ($BytesToProcess -ge $fs.Length -or $BytesOffset -ge $fs.Length) {
            throw [InvalidOperationException]::new('Out of stream.')
          }
          # offset has been defined
          if ($BytesOffset -gt 0) { [void]$fs.Seek($BytesOffset, [SeekOrigin]::Begin) }
          # bytes to process
          $buf = [Byte[]]::new(($fs.Length, $BytesToProcess)[$BytesToProcess -gt 0])
          [void]$fs.Read($buf, 0, $buf.Length)
          # show printable strings
          ([Regex]"[\x20-\x7E]{$StringLength,}").Matches(
            [Encoding]::"U$(('TF7', 'nicode')[$Unicode.IsPresent])".GetString($buf)
          ).ForEach{
            if ($StringOffset) { '{0}:{1}' -f $_.Index, $_.Value } else { $_.Value }
          }
        }
        catch { Write-Verbose $_ }
        finally {
          if ($fs) { $fs.Dispose() }
        }
      }
    }
  }
  process {}
  end {
    .({Find-Strings (Get-Item -LiteralPath $LiteralPath)},{
      Find-Strings ((Get-Item $Path -ErrorAction 0), $Path)[$PipelineInput]
    })[$PSCmdlet.ParameterSetName -eq 'Path']
  }
}

Export-ModuleMember -Alias strings -Function Get-Strings