Skip to main content

A Windows PowerShell equivalent to theLinux shasum command.

#
# getopt.ps1
# https://github.com/lukesampson/psutils/blob/master/getopt.ps1
#

# adapted from http://hg.python.org/cpython/file/2.7/Lib/getopt.py
# argv:
#    array of arguments
# shortopts:
#    string of single-letter options. options that take a parameter
#    should be follow by ':'
# longopts:
#    array of strings that are long-form options. options that take
#    a parameter should end with '='
# returns @(opts hash, rem_args array, error string)
function getopt($argv, $shortopts, $longopts) {
    Set-StrictMode -Off;

    $opts = @{}; $rem = @()

    function err($msg) {
        $opts, $rem, $msg
    }

    # ensure these are arrays
    $argv = @($argv)
    $longopts = @($longopts)

    for($i = 0; $i -lt $argv.length; $i++) {
        $arg = $argv[$i]
        # don't try to parse object arguments
        if($arg -is [array]) { $rem += ,$arg; continue }
        if($arg -is [int]) { $rem += $arg; continue }

        if($arg.startswith('--')) {
            $name = $arg.substring(2)

            $longopt = $longopts | ? { $_ -match "^$name=?$" }

            if($longopt) {
                if($longopt.endswith('=')) { # requires arg
                    if($i -eq $argv.length - 1) {
                        return err "option --$name requires an argument"
                    }
                    $opts.$name = $argv[++$i]
                } else {
                    $opts.$name = $true
                }
            } else {
                return err "option --$name not recognized"
            }
        } elseif($arg.startswith('-') -and $arg -ne '-') {
            for($j = 1; $j -lt $arg.length; $j++) {
                $letter = $arg[$j].tostring()

                if($shortopts -match "$letter`:?") {
                    $shortopt = $matches[0]
                    if($shortopt[1] -eq ':') {
                        if($j -ne $arg.length -1 -or $i -eq $argv.length - 1) {
                            return err "option -$letter requires an argument"
                        }
                        $opts.$letter = $argv[++$i]
                    } else {
                        $opts.$letter = $true
                    }
                } else {
                    return err "option -$letter not recognized"
                }
            }
        } else {
            $rem += $arg
        }
    }

    $opts, $rem
}

#
# shasum.ps1
# https://github.com/lukesampson/psutils/blob/master/shasum.ps1
#

Set-StrictMode -Off;

function usage {
"Usage: shasum [OPTION] [FILE]...
   or: shasum [OPTION] --check [FILE]
Print or check SHA checksums.
With no FILE, or when FILE is -, read standard input.

 -a, --algorithm    1 (default), 256, 384, 512
 -b, --binary       read files in binary mode (default)
 -c, --check        check SHA sums against given list
 -t, --text         read files in text mode
 -p, --portable     read files in portable mode
                        produces same digest on Windows/Unix/Mac

The following two options are useful only when verifying checksums:

 -s, --status       don't output anything, status code shows success
 -w, --warn         warn about improperly formatted SHA checksum lines

 -h, --help         display this help and exit
 -v, --version      output version information and exit

The sums are computed as described in FIPS PUB 180-2.  When checking,
the input should be a former output of this program.  The default mode
is to print a line with checksum, a character indicating type (`*'
for binary, `?' for portable, ` ' for text), and name for each FILE."
}

$algs = @(1,256,384,512)

function compute_hash($file, $algname) {
    $alg = [system.security.cryptography.hashalgorithm]::create($algname)
    $fs = [system.io.file]::openread($file)
    try {
        $hexbytes = $alg.computehash($fs) | % { $_.tostring('x2') }
        [string]::join('', $hexbytes)
    } finally {
        $fs.dispose()
        $alg.dispose()
    }
}

function write_hash($file, $alg, $mode) {
    if($file -match '\*') { "shasum: $file`: invalid argument"; return }
    if(!(test-path $file -pathtype leaf)) { "shasum: $file`: no such file"; return }

    $hash = compute_hash (resolve-path $file) "SHA$alg"
    $mode_indicator = switch($mode) {
        'binary' { '*' }
        'text' { ' '}
        'portable' { '?' }
    }
    "$hash $mode_indicator$file"
}

function verify($checkfile) {
    if(!(test-path $checkfile -pathtype leaf)) { "shasum: $file`: no such file"; return }

    $len2alg = @{ 40 = 1; 56 = 224; 64 = 256; 96 = 384; 128 = 512 }

    $lines = gc $checkfile
    $lines | % {
        if($_ -match '([^ ]+) (.)(.*)') {
            $hash, $mode, $file = $matches[1..3]
            $alg = $len2alg[$hash.length]
            if($algs -notcontains $alg) {
                "$file`: FAILED: SHA-$alg not supported"; return
            }
            if(!(test-path $file -pathtype leaf)) {
                "$file`: FAILED: no such file"; return
            }
            $match = $hash -eq (compute_hash (resolve-path $file) "SHA$alg")
            "$file`: $(if($match) { 'OK' } else { 'FAILED' })"
        }
    }
}

$opt, $files, $err = getopt $args 'a:bcpth' @('algorithm=','binary','check','text','portable','help')
if($err) { "shasum: $err"; exit 1 }

if($opt.h -or $opt.help) { usage; exit 0 }

if(!$files) { "shasum: file is required"; exit 1 }

$alg = $opt.algorithm;
if(!$alg) { $alg = $opt.a }
if(!$alg) { $alg = 1 }
if($algs -notcontains $alg) { "shasum: invalid algorithm"; exit 1 }

$mode = 'binary'
if($opt.t -or $opt.text) { $mode = 'text' }
if($opt.p -or $opt.portable) { $mode = 'portable' }

$check = $opt.c -or $opt.check
if($check) {
    verify @($files)[0]
} else {
    $files | % { write_hash $_ $alg $mode }
}

exit 0