Тема: PowerShell: пример вычисления хешей файла различными способами
С появлением в PowerShell 4.0 командлета Get-FileHash получение хеша файла не является проблемой, но когда я начал изучать PowerShell, на Висте, вроде, был ещё только .Net 2.0 со своим весьма скудным пространством имён и вычисление хеша разными способами казалось интересной задачей. Тогда и написал этот скрипт, стараясь следовать тогдашним правилам написания и оформления кода. В то время у меня все мои скрипты (не только PowerShell, но и WSH) подписывались (что, как потом оказалось, ломало нормальную работу сторонних скриптов) и я выбрал тот вариант оформления кода, который мог обеспечить получение информации по Get-Help для подписанного скрипта, то есть "описание в начале". Скрипт, возможно, интересен с точки зрения решения задачи различными способами и влияния выбранного способа на скорость работы, но, конечно, сейчас это имеет, разве что, исторический интерес. Тем не менее привожу скрипт здесь, предварительно поправив синтаксические неточности в описании, возможно он кого-то заинтересует. Check-Hash.ps1:
<#.SYNOPSIS
Выводит либо проверяет MD5, SHA1, SHA256, SHA384, SHA512 хеши указанного файла.
.DESCRIPTION
Выводит либо проверяет MD5, SHA1, SHA256, SHA384, SHA512 хеши указанного файла.
.PARAMETER Path
Задает путь к файлу.
.PARAMETER MD5
Вычисляет и выводит MD5 хеш.
.PARAMETER SHA1
Вычисляет и выводит SHA1 хеш.
.PARAMETER SHA256
Вычисляет и выводит SHA256 хеш.
.PARAMETER SHA384
Вычисляет и выводит SHA384 хеш.
.PARAMETER SHA512
Вычисляет и выводит SHA512 хеш.
.PARAMETER Common
Вычисляет и выводит MD5 и SHA1 хеш. Если ни один параметр MD5, SHA1, SHA256, SHA384 или SHA512 не задан, считается, что задан параметр Common.
.PARAMETER All
Вычисляет и выводит MD5, SHA1, SHA256, SHA384 и SHA512 хеш.
.PARAMETER CheckMD5
Задает хеш MD5, который следует проверить на совпадение с вычисленным.
.PARAMETER CheckSHA1
Задает хеш SHA1, который следует проверить на совпадение с вычисленным.
.PARAMETER CheckSHA256
Задает хеш SHA256, который следует проверить на совпадение с вычисленным.
.PARAMETER CheckSHA384
Задает хеш SHA384, который следует проверить на совпадение с вычисленным.
.PARAMETER CheckSHA512
Задает хеш SHA512, который следует проверить на совпадение с вычисленным.
.PARAMETER Action
Задает действие, которое следует выполнить. Допустимые значения: "ComputeMD5", "ComputeSHA1", "ComputeSHA256", "ComputeSHA384", "ComputeSHA512", "MD5", "SHA1", "SHA256", "SHA384", "SHA512" - для вычисления соответствующего хеша; "CheckMD5", "CheckSHA1", "CheckSHA256", "CheckSHA384", "CheckSHA512" - для проверки хеша, заданного параметром GivenHash. Регистр символов параметра Action значения не имеет.
.PARAMETER GivenHash
Задает хеш, который следует проверить на совпадение с вычисленным.
.PARAMETER Mode
Задает режим вычисления нескольких хешей. Допустимые значения: 0 (значение по умолчанию) - файл читается однократно, все суммы расчитываются по мере чтения файла; 1 - каждая из хеш сумм вычисляется последовательно, файл читается столько раз, сколько требуется вычислить хешей и обрабатывается как поток, а не читается частями (применяется по умолчанию, если тербуется вычислить только одну хеш сумму); 2 - расчет выполняется параллельно в фоновых заданиях PowerShell, каждое задание вычисляет один хеш и работает в режиме 1 (режим 2 не действует, если необходимо вычислить только одну хеш сумму).
.PARAMETER ScriptArguments
Задает хеш-таблицу, которая используется для передачи дополнительной информации (общие и некоторые необязательные параметры) из основного скрипта в фоновые задания. Ключи хеш-таблицы задают имена переменных скрипта, значения которых должны быть установлены равными значениям хеш-таблицы. Не все переменные могут быть изменены таким способом.
.PARAMETER ToLower
Выводит хеш суммы строчными буквами.
.INPUTS
System.String
Строку, содержащую путь к файлу, можно передать скрипту Check-Hash по конвейеру.
.OUTPUTS
System.Collections.Hashtable
Выводит хеш-таблицу, содержащую вычисленные хеш суммы для файла или логические значения, отражающие успешность проверки заданных хеш сумм.
.NOTES
1. Быстрое прерывание вычислений по Control-Break возможно только в режиме 0. Этот режим можно задать и при вычислении одного хеша.
2. Когда проверяются несколько хеш сумм, выходная хеш-таблица будет содержать значение Status равное $true, если все хеш суммы совпали и $false в противном случае.
3. В режиме 2 из заданных обших параметров в фоновые задания передаются только параметры Verbose, WarningActon, ErrorAction, а также соответствующие им привилегированные переменные, если их значения не равны значениям по умолчанию.
#>
[CmdletBinding(DefaultParameterSetName='Compute')]
Param( [parameter(Mandatory=$true, Position=0, ValueFromPipeline=$true, ValueFromPipelineByPropertyName=$true)]
[String][Alias("FullName")][ValidateNotNullOrEmpty()]$Path,
[parameter(ParameterSetName='Compute')][Switch]$MD5,
[parameter(ParameterSetName='Compute')][Switch]$SHA1,
[parameter(ParameterSetName='Compute')][Switch]$SHA256,
[parameter(ParameterSetName='Compute')][Switch]$SHA384,
[parameter(ParameterSetName='Compute')][Switch]$SHA512,
[parameter(ParameterSetName='Compute')][Switch]$Common,
[parameter(ParameterSetName='Compute')][Switch]$All,
[parameter(ParameterSetName='Check')][String][ValidateLength(32,32)][ValidatePattern('[0-9a-fA-F]{32}')]$CheckMD5,
[parameter(ParameterSetName='Check')][String][ValidateLength(40,40)][ValidatePattern('[0-9a-fA-F]{40}')]$CheckSHA1,
[parameter(ParameterSetName='Check')][String][ValidateLength(64,64)][ValidatePattern('[0-9a-fA-F]{64}')]$CheckSHA256,
[parameter(ParameterSetName='Check')][String][ValidateLength(96,96)][ValidatePattern('[0-9a-fA-F]{96}')]$CheckSHA384,
[parameter(ParameterSetName='Check')][String][ValidateLength(128,128)][ValidatePattern('[0-9a-fA-F]{128}')]$CheckSHA512,
[parameter(ParameterSetName='Compute')][parameter(ParameterSetName='Check')]
[Int][ValidateRange(0,2)][ValidateScript({$script:ModeSet = $true; $true})]$Mode=0,
[parameter(ParameterSetName='SingleSum', Mandatory=$true, Position=1)]
[String][ValidatePattern('^(Check|Compute)?(MD5|SHA1|SHA256|SHA384|SHA512){1}$')]$Action,
[parameter(ParameterSetName='SingleSum', Position=2)][String]$GivenHash,
[parameter(ParameterSetName='SingleSum')][Hashtable]$ScriptArguments,
[Switch]$ToLower)
function OutHashTable($out){
if($out){$HashTable = @{}
foreach($i in $out){$HashTable += @{([Array]$i.Keys)[0]=([Array]$i.Values)[0]}}
if(($v = [Array]$HashTable.Values)[0] -is [Boolean] -and $v.length -gt 1){
$Status = $true
foreach($i in $v){if(!$i){$Status = $false; break}}
$HashTable += @{Status=$Status}
}
}
$HashTable
}
function GetTypes($a){$t=@(); foreach($i in $a){$t += @($i.type)}; $t -join ', '}
$Path = Resolve-Path $Path
if(!(Test-Path $Path)){Throw "Не найден:""$Path"""}
$a = @()
switch($PsCmdlet.ParameterSetName){
'Check'{
if($CheckMD5){$a += @{type='MD5'; value=$CheckMD5}}
if($CheckSHA1){$a += @{type='SHA1'; value=$CheckSHA1}}
if($CheckSHA256){$a += @{type='SHA256'; value=$CheckSHA256}}
if($CheckSHA384){$a += @{type='SHA384'; value=$CheckSHA384}}
if($CheckSHA512){$a += @{type='SHA512'; value=$CheckSHA512}}
}
'Compute'{
if($MD5 -or $Common -or $All){$a += @{type='MD5'}}
if($SHA1 -or $Common -or $All){$a += @{type='SHA1'}}
if($SHA256 -or $All){$a += @{type='SHA256'}}
if($SHA384 -or $All){$a += @{type='SHA384'}}
if($SHA512 -or $All){$a += @{type='SHA512'}}
}
'SingleSum'{
if(($Action -notmatch 'Check' -and !$GivenHash) -or ($Action -match 'Check' -and`
(($Action -match 'MD5' -and $GivenHash -match '[0-9a-fA-F]{32}') -or`
($Action -match 'SHA1' -and $GivenHash -match '[0-9a-fA-F]{40}') -or`
($Action -match 'SHA256' -and $GivenHash -match '[0-9a-fA-F]{64}') -or`
($Action -match 'SHA384' -and $GivenHash -match '[0-9a-fA-F]{96}') -or`
($Action -match 'SHA512' -and $GivenHash -match '[0-9a-fA-F]{128}')))){
$h = @{type=$Action -replace 'Check|Compute', ''}
if($GivenHash){$h += @{value=$GivenHash}}
$a += $h
}else{Throw "Значение параметра GivenHash задано неверно!"}
if($ScriptArguments){
$ScriptArguments.GetEnumerator() | Foreach-Object {
if('a', 'Path', 'value', 'out' -notcontains $_.Key){
if('Verbose', 'ErrorAction' -contains $_.Key){
$Key = "$($_.Key)Preference"
$NewValue = if($_.Value -isnot [Boolean]){$_.Value}
else{"$(if(!$_.Value){'Silently'})Continue"}
}
else{ $Key = "$($_.Key)"
if($Key -eq 'WarningAction'){$Key = 'WarningPreference'}
$NewValue = $_.Value
}
Set-Variable "$Key" -Value $NewValue -Scope Script
}
}
}
}
}
if(!$a.Count){$a = @(@{type='MD5'}, @{type='SHA1'})}
$Script = $MyInvocation.MyCommand.Path
$bufsize = 16kb
$buffer = New-Object Byte[] -ArgumentList $bufsize
if($a.Count -eq 1 -and (!$ModeSet -or ($ModeSet -and $Mode -gt 0))){
$type = $a[0].type
if($a[0].value){$value = $a[0].value}
Write-Verbose "Расчет $type выполняется в режиме 1"
$file = New-Object System.IO.FileStream($Path, [System.IO.FileMode]::Open, [System.IO.FileAccess]::Read,
[System.IO.FileShare]::Read, $bufsize)
$CSP = New-Object "System.Security.Cryptography.${type}CryptoServiceProvider"
if(!$ToLower){$type = $type.ToUpper(); $x = 'X2'}else{$type = $type.ToLower(); $x = 'x2'}
$hash = ($CSP.ComputeHash($file) | ForEach-Object {$_.ToString($x)}) -join ''
$file.Close()
if($value){@{$type=$hash -eq $value}}else{@{$type=$hash}}
}elseif($Mode -eq 0){
Write-Verbose "Расчет $(GetTypes $a) выполняется в режиме 0"
$file = New-Object System.IO.FileStream($Path, [System.IO.FileMode]::Open, [System.IO.FileAccess]::Read,
[System.IO.FileShare]::Read, $bufsize)
for($i = 0; $i -lt $a.length; $i++){
$a[$i] += @{CSP=New-Object "System.Security.Cryptography.$($a[$i].type)CryptoServiceProvider"}
}
while($bytes = $file.Read($buffer, 0, $bufsize)){
for($i = 0; $i -lt $a.length; $i++){[Void]$a[$i].CSP.TransformBlock($buffer, 0, $bytes, $buffer, 0)}
}
for($i = 0; $i -lt $a.length; $i++){[Void]$a[$i].CSP.TransformFinalBlock($buffer, 0, 0)}
$file.Close()
$out = @()
if(!$ToLower){$x = 'X2'}else{$x = 'x2'}
for($i = 0; $i -lt $a.length; $i++){
$hash = ($a[$i].CSP.Hash | ForEach-Object {$_.ToString($x)}) -join ''
$type = if(!$ToLower){$a[$i].type.ToUpper()}else{$a[$i].type.ToLower()}
$out += if($a[$i].value){@{$type=$hash -eq $a[$i].value}}else{@{$type=$hash}}
}
OutHashTable $out
}elseif($Mode -eq 1){
Write-Verbose "Расчет $(GetTypes $a) выполняется в режиме 1"
$out = @()
foreach($i in $a){
$out += & "$Script" $Path "$(if($i.value){"Check"})$($i.type)" "$(if($i.value){$i.value})" -ToLower:$ToLower
}
OutHashTable $out
}elseif($Mode -eq 2){
Write-Verbose "Расчет $(GetTypes $a) выполняется в режиме 2"
$jobs = @()
$ScriptArguments_ = ""
$PSBoundParameters.GetEnumerator() |
Foreach-Object {if($_.Key -notmatch 'Path|MD5|SHA1|SHA256|SHA384|SHA512|Common|All|Mode|^Action$|GivenHash|ScriptArguments'){
$v = "$($_.Value.ToString())" -replace "'", "''"
$ScriptArguments_ += "`n$($_.Key)=$(if($_.Value -eq $null){'$null'}else{"[$($_.Value.GetType().Name)]'$v'"})"
}}
if($VerbosePreference -ne 'SilentlyContinue' -and $ScriptArguments_ -notmatch '\nVerbose='){
$ScriptArguments_ += "`nVerbosePreference=[String]'$VerbosePreference'"
}
if($WarningPreference -ne 'Continue' -and $ScriptArguments_ -notmatch '\nWarningAction='){
$ScriptArguments_ += "`nWarningPreference=[String]'$WarningPreference'"
}
if($ErrorActionPreference -ne 'Continue' -and $ScriptArguments_ -notmatch '\nErrorAction='){
$ScriptArguments_ += "`nErrorActionPreference=[String]'$ErrorActionPreference'"
}
foreach($i in $a){$jobs += Start-Job -ScriptBlock {param($p1, $p2, $p3, $p4, $p5) $p=@{};
(ConvertFrom-StringData $p5).GetEnumerator() | Foreach-Object {
$v = $_.Value -replace '\[SwitchParameter\]', '[Boolean]' -replace '\[ActionPreference\]', '[String]'
if($v -match "^\[Boolean\]'([^']+)'$"){$v = '$' + $Matches[1]}
$p += Invoke-Expression "@{$($_.Key)=$v}"
}
& "$p1" $p2 $p3 $p4 -ScriptArguments $p}`
-ArgumentList $Script, $Path, "$(if($i.value){"Check"})$($i.type)", "$(if($i.value){$i.value})", $ScriptArguments_}
if($jobs){try{$out = Wait-Job $jobs | Receive-Job}finally{Remove-Job $jobs -Force}}
OutHashTable $out
}else{Throw "Неизвестный режим:$Mode!"}