MOBZHash code

This code was downloaded from MOBZystems, Home of Tools. No license, use at will. And at your own risk!

Option Strict On
Option Explicit On

Imports System.Collections.Generic
Imports System.IO
Imports System.Security.Cryptography

''' 
''' Console application to display hash values for file contents.
''' Also supports finding and displaying files with identical hashes
''' 
''' 
''' Return values:
''' 
''' 0 - OK
''' 1- Argument error
''' 2 - Run time error
''' 
Module MOBZHash
  Function Main(arguments() As String) As Integer
    ' Construct a title
    Dim title As String = String.Format(
      "{0} v{1} ({2}-bit) by MOBZystems - http://www.mobzystems.com/Tools/MOBZHash",
      My.Application.Info.Title,
      My.Application.Info.Version.ToString(3),
      IntPtr.Size * 8
    )

    Try

      ' If no arguments specified, print usage
      If arguments.Length = 0 Then
        Console.WriteLine(title)
        Console.WriteLine()
        Console.WriteLine("Usage:")
        Console.WriteLine()
        Console.WriteLine("MOBZHash [-v] [-r] [-d] hash-type [file-spec [file-spec ...]]")
        Console.WriteLine()
        Console.WriteLine("hash-type: hash type to use. All .NET hash algorihms are supported")
        Console.WriteLine("           Examples: MD5, SHA1 (default), SHA256, SHA384, SHA512")
        Console.WriteLine("file-spec: A file name or wildcard. Defaults to all files in current directory")
        Console.WriteLine()
        Console.WriteLine("Switches:")
        Console.WriteLine("-v: verbose")
        Console.WriteLine("-r: recursive")
        Console.WriteLine("-d: find duplicates. Display a list of all files with an identical hash")

        ' No error
        Return 0
      End If

      ' Parse the command line

      ' The first non-switch argument is the hash name
      Dim hashName As String = Nothing
      ' List of file specs
      Dim fileSpecs As New List(Of String)
      ' List of switches
      Dim switches As New List(Of String)

      ' Separate switches from arguments, isolate hash name
      For Each arg As String In arguments
        If arg.StartsWith("-") OrElse arg.StartsWith("/") Then
          switches.Add(arg.Substring(1).ToUpperInvariant())
        ElseIf hashName Is Nothing Then
          hashName = arg.ToUpperInvariant()
        Else
          fileSpecs.Add(arg)
        End If
      Next

      ' Use SHA1 if no hash specified
      If hashName Is Nothing Then
        hashName = "SHA1"
      End If

      ' See if we can create a hash algorithm for the specified hash name
      Dim hasher As HashAlgorithm = Nothing
      Try
        hasher = DirectCast(CryptoConfig.CreateFromName(hashName), HashAlgorithm)
      Catch ex As Exception
        ' We get an exception if the hash exists, but is not a hash algorithm (e.g. RSA)
        Console.Error.WriteLine(
          String.Format("Invalid hash type '{0}'. Valid hash types are MD5, SHA1, " +
                        "SHA256, SHA256, SHA384, SHA512", hashName))
        ' Argument error
        Return 1
      End Try

      ' Did we fail silently? (Hash name is unknown)
      If hasher Is Nothing Then
        Console.Error.WriteLine(String.Format("Uknown hash type '{0}'", hashName))
        ' Argument error
        Return 1
      End If

      ' Parse the switches:

      Dim verbose As Boolean = False
      Dim recursive As Boolean = False
      Dim duplicates As Boolean = False

      For Each switch As String In switches
        Select Case switch
          Case "V"
            verbose = True
          Case "R"
            recursive = True
          Case "D"
            duplicates = True
          Case Else
            Console.Error.WriteLine("Invalid switch '{0}'", switch)
            ' Argument error
            Return 1
        End Select
      Next

      ' Process all file specifications

      ' Provide a default file spec if we didn't supply one
      If fileSpecs.Count = 0 Then
        fileSpecs.Add("*.*")
      End If

      ' Build a dictionary of hashes if we are looking for duplicates
      Dim hashes As Dictionary(Of String, List(Of String)) = Nothing

      If duplicates Then
        hashes = New Dictionary(Of String, List(Of String))
      End If

      If verbose Then
        Console.WriteLine(title)
        Console.WriteLine()
      End If

      ' Loop over the file specs
      For Each fileSpec In fileSpecs
        Dim pathName As String
        Dim fileMask As String

        ' Did we specify a directory?
        If IO.Directory.Exists(fileSpec) Then
          ' Use the name of the directory
          pathName = fileSpec
          ' and a file mask of *.*
          fileMask = "*.*"
        Else
          ' Separate directory from file mask
          pathName = Path.GetDirectoryName(fileSpec)
          fileMask = Path.GetFileName(fileSpec)

          ' Did we spcify a file name only?
          If String.IsNullOrEmpty(pathName) Then
            ' Use the current directory
            pathName = Environment.CurrentDirectory
          End If
        End If

        ' Show some text in verbose mode
        If verbose Then
          Console.WriteLine(
            String.Format(
              "{0} {1} hash for {2}{3}...",
              If(duplicates, "Finding duplicates by", "Computing"),
              hashName,
              fileSpec,
              If(recursive, " and subdirectories", "")
            )
          )
        End If

        ' Find all files corresponding to the file mask in the specified path,
        ' possibly recursive
        Dim dir As New DirectoryInfo(pathName)
        Dim files() As FileInfo = Nothing

        Try
          files = dir.GetFiles(
            fileMask,
            If(recursive, SearchOption.AllDirectories, SearchOption.TopDirectoryOnly)
          )
        Catch ex As Exception
          Console.Error.WriteLine(
            String.Format(
              "Could not access '{0}': {1}",
              pathName,
              ex.Message
            )
          )
        End Try

        If files IsNot Nothing Then
          ' Process all files
          For Each file As FileInfo In files

            ' Skip hidden files
            If (file.Attributes And FileAttributes.Hidden) = 0 Then

              ' Set up the file name to display
              Dim relativeFileName As String = file.FullName.Substring(dir.FullName.Length + 1)

              ' Use the relative name for display purposes if we have a single file spec.
              ' Otherwise, use the full name of the file
              Dim displayName As String = relativeFileName
              If fileSpecs.Count > 1 Then
                displayName = file.FullName
              End If

              Try

                Using stream As Stream = System.IO.File.OpenRead(file.FullName)
                  ' Compute the hash on the stream of the file
                  Dim hash As Byte() = hasher.ComputeHash(stream)
                  ' Convert the hash to a string
                  Dim hashString As String = BitConverter.ToString(hash)

                  ' Are we collecting duplicates?
                  If duplicates Then

                    ' Store the hash and the file name in the hashes
                    Dim fileList As List(Of String) = Nothing

                    ' Do we know this hash?
                    If hashes.TryGetValue(hashString, fileList) Then

                      ' Add this file if it isn't in the list already (case insensitive!)
                      If Not fileList.Contains(
                        displayName,
                        StringComparer.InvariantCultureIgnoreCase
                      ) Then
                        fileList.Add(displayName)
                      End If

                    Else

                      ' New hash - create a new file list
                      fileList = New List(Of String)
                      ' Add this file
                      fileList.Add(displayName)
                      ' Store in hashes
                      hashes.Add(hashString, fileList)

                    End If

                  Else

                    ' Not finding duplicates - simply list hash and file name
                    Console.WriteLine(String.Format("{0}: {1}", hashString, displayName))

                  End If

                End Using

              Catch ex As Exception
                Console.Error.WriteLine(
                  String.Format(
                    "Error computing {0} hash for file '{1}': {2}",
                    hashName,
                    displayName,
                    ex.Message
                  )
                )

              End Try

            End If ' Not hidden

          Next file

        End If ' If no error getting files

      Next fileSpec

      ' We're done processing all file specs.
      ' For duplicates, display all lists containing more than one item:
      If duplicates Then
        ' Loop over all hashes
        For Each hashString As String In hashes.Keys
          ' Get the list of files with this hash
          Dim fileList As List(Of String) = hashes(hashString)
          ' More than one? Display them
          If fileList.Count > 1 Then
            Console.WriteLine("{0} {1}: {2} files", hashName, hashString, fileList.Count)
            For Each filename In fileList
              Console.WriteLine(String.Format("- {0}", filename))
            Next
          End If
        Next
      End If

      ' OK
      Return 0

    Catch ex As Exception
      Console.Error.WriteLine(
        String.Format(
          "Hash: Generic error: {0}",
          ex.Message
        )
      )

      ' Run-time eror
      Return 2
    End Try
  End Function
End Module