function Get-FileHash { [CmdletBinding(DefaultParameterSetName = "Path", HelpURI = "https://go.microsoft.com/fwlink/?LinkId=517145")] param( [Parameter(Mandatory, ParameterSetName="Path", Position = 0)] [System.String[]] $Path, [Parameter(Mandatory, ParameterSetName="LiteralPath", ValueFromPipelineByPropertyName = $true)] [Alias("PSPath")] [System.String[]] $LiteralPath, [Parameter(Mandatory, ParameterSetName="Stream")] [System.IO.Stream] $InputStream, [ValidateSet("SHA1", "SHA256", "SHA384", "SHA512", "MACTripleDES", "MD5", "RIPEMD160")] [System.String] $Algorithm="SHA256" ) begin { # Construct the strongly-typed crypto object # First see if it has a FIPS algorithm $hasherType = "System.Security.Cryptography.${Algorithm}CryptoServiceProvider" -as [Type] if ($hasherType) { $hasher = $hasherType::New() } else { # Check if the type is supported in the current system $algorithmType = "System.Security.Cryptography.${Algorithm}" -as [Type] if ($algorithmType) { if ($Algorithm -eq "MACTripleDES") { $hasher = $algorithmType::New() } else { $hasher = $algorithmType::Create() } } else { $errorId = "AlgorithmTypeNotSupported" $errorCategory = [System.Management.Automation.ErrorCategory]::InvalidArgument $errorMessage = [Microsoft.PowerShell.Commands.UtilityResources]::AlgorithmTypeNotSupported -f $Algorithm $exception = [System.InvalidOperationException]::New($errorMessage) $errorRecord = [System.Management.Automation.ErrorRecord]::New($exception, $errorId, $errorCategory, $null) $PSCmdlet.ThrowTerminatingError($errorRecord) } } function GetStreamHash { param( [System.IO.Stream] $InputStream, [System.String] $RelatedPath, [System.Security.Cryptography.HashAlgorithm] $Hasher) # Compute file-hash using the crypto object [Byte[]] $computedHash = $Hasher.ComputeHash($InputStream) [string] $hash = [BitConverter]::ToString($computedHash) -replace '-','' if ($RelatedPath -eq $null) { $retVal = [PSCustomObject] @{ Algorithm = $Algorithm.ToUpperInvariant() Hash = $hash } } else { $retVal = [PSCustomObject] @{ Algorithm = $Algorithm.ToUpperInvariant() Hash = $hash Path = $RelatedPath } } $retVal.psobject.TypeNames.Insert(0, "Microsoft.Powershell.Utility.FileHash") $retVal } } process { if($PSCmdlet.ParameterSetName -eq "Stream") { GetStreamHash -InputStream $InputStream -RelatedPath $null -Hasher $hasher } else { $pathsToProcess = @() if($PSCmdlet.ParameterSetName -eq "LiteralPath") { $pathsToProcess += Resolve-Path -LiteralPath $LiteralPath | Foreach-Object ProviderPath } if($PSCmdlet.ParameterSetName -eq "Path") { $pathsToProcess += Resolve-Path $Path | Foreach-Object ProviderPath } foreach($filePath in $pathsToProcess) { if(Test-Path -LiteralPath $filePath -PathType Container) { continue } try { # Read the file specified in $FilePath as a Byte array [system.io.stream]$stream = [system.io.file]::OpenRead($filePath) GetStreamHash -InputStream $stream -RelatedPath $filePath -Hasher $hasher } catch [Exception] { $errorMessage = [Microsoft.PowerShell.Commands.UtilityResources]::FileReadError -f $FilePath, $_ Write-Error -Message $errorMessage -Category ReadError -ErrorId "FileReadError" -TargetObject $FilePath return } finally { if($stream) { $stream.Dispose() } } } } } } <# This cmdlet is used to create a new temporary file in $env:temp #> function New-TemporaryFile { [CmdletBinding( HelpURI='https://go.microsoft.com/fwlink/?LinkId=526726', SupportsShouldProcess=$true)] [OutputType([System.IO.FileInfo])] Param() Begin { try { if($PSCmdlet.ShouldProcess($env:TEMP)) { $tempFilePath = [System.IO.Path]::GetTempFileName() } } catch { $errorRecord = [System.Management.Automation.ErrorRecord]::new($_.Exception,"NewTemporaryFileWriteError", "WriteError", $env:TEMP) Write-Error -ErrorRecord $errorRecord return } if($tempFilePath) { Get-Item $tempFilePath } } } <# This cmdlet is used to generate a new guid #> function New-Guid { [CmdletBinding(HelpURI='https://go.microsoft.com/fwlink/?LinkId=526920')] [OutputType([System.Guid])] Param() Begin { [Guid]::NewGuid() } } <############################################################################################ # Format-Hex cmdlet helps in displaying the Hexadecimal equivalent of the input data. ############################################################################################> function Format-Hex { [CmdletBinding( DefaultParameterSetName="Path", HelpUri="https://go.microsoft.com/fwlink/?LinkId=526919")] [Alias("fhx")] [OutputType("Microsoft.PowerShell.Commands.ByteCollection")] param ( [Parameter (Mandatory=$true, Position=0, ParameterSetName="Path")] [ValidateNotNullOrEmpty()] [string[]] $Path, [Parameter (Mandatory=$true, ParameterSetName="LiteralPath")] [ValidateNotNullOrEmpty()] [Alias("PSPath")] [string[]] $LiteralPath, [Parameter(Mandatory=$true, ParameterSetName="ByInputObject", ValueFromPipeline=$true)] [Object] $InputObject, [Parameter (ParameterSetName="ByInputObject")] [ValidateSet("Ascii", "UTF32", "UTF7", "UTF8", "BigEndianUnicode", "Unicode")] [string] $Encoding = "Ascii", [Parameter(ParameterSetName="ByInputObject")] [switch]$Raw ) begin { $bufferSize = 16 $inputStreamArray = [System.Collections.ArrayList]::New() <############################################################################################ # The ConvertToHexadecimalHelper is a helper method used to fetch unicode bytes from the # input data and display the hexadecimial representaion of the of the input data in bytes. ############################################################################################> function ConvertToHexadecimalHelper { param ( [Byte[]] $inputBytes, [string] $path, [Uint32] $offset ) # This section is used to display the hexadecimal # representaion of the of the input data in bytes. if($inputBytes -ne $null) { $byteCollectionObject = [Microsoft.PowerShell.Commands.ByteCollection]::new($offset, $inputBytes, $path) Write-Output -InputObject $byteCollectionObject } } <############################################################################################ # The ProcessFileContent is a helper method used to fetch file contents in blocks and # process it to support displaying hexadecimal formating of the fetched content. ############################################################################################> function ProcessFileContent { param ( [string] $filePath, [boolean] $isLiteralPath ) if($isLiteralPath) { $resolvedPaths = Resolve-Path -LiteralPath $filePath } else { $resolvedPaths = Resolve-Path -Path $filePath } # If Path resolution has failed then a corresponding non-terminating error is # written to the pipeline. We continue processing any remaining files. if($resolvedPaths -eq $null) { return } if($resolvedPaths.Count -gt 1) { # write a non-terminating error message indicating that path specified is resolving to multiple file system paths. $errorMessage = [Microsoft.PowerShell.Commands.UtilityResources]::FormatHexResolvePathError -f $filePath Write-Error -Message $errorMessage -Category ([System.Management.Automation.ErrorCategory]::InvalidData) -ErrorId "FormatHexResolvePathError" } $targetFilePath = $resolvedPaths.ProviderPath if($targetFilePath -ne $null) { $buffer = [byte[]]::new($bufferSize) try { try { $currentFileStream = [System.IO.File]::Open($targetFilePath, [System.IO.FileMode]::Open, [System.IO.FileAccess]::Read) } catch { # Failed to access the file. Write a non terminating error to the pipeline # and move on with the remaining files. $exception = $_.Exception if($null -ne $_.Exception -and $null -ne $_.Exception.InnerException) { $exception = $_.Exception.InnerException } $errorRecord = [System.Management.Automation.ErrorRecord]::new($exception,"FormatHexFileAccessError", ([System.Management.Automation.ErrorCategory]::ReadError), $targetFilePath) $PSCmdlet.WriteError($errorRecord) } if($null -ne $currentFileStream) { $srcStream = [System.IO.BinaryReader]::new($currentFileStream) $displayHeader = $true $offset = 0 $blockCounter = 0 while($numberOfBytesRead = $srcStream.Read($buffer, 0, $bufferSize)) { # send only the bytes that have been read # if we send the whole buffer, we'll have extraneous bytes # at the end of an incomplete group of 16 bytes if ( $numberOfBytesRead -eq $bufferSize ) { # under some circumstances if we don't copy the buffer # and the results are stored to a variable, the results are not # correct and one object replicated in all the output objects ConvertToHexadecimalHelper ($buffer.Clone()) $targetFilePath $offset } else { # handle the case of the partial (and probably last) buffer $bytesReadBuffer = [byte[]]::New($numberOfBytesRead) [Array]::Copy($buffer,0, $bytesReadBuffer,0,$numberOfBytesRead) ConvertToHexadecimalHelper $bytesReadBuffer $targetFilePath $offset } $displayHeader = $false $blockCounter++; # Updating the offset value. $offset = $blockCounter*0x10 } } } finally { If($null -ne $currentFileStream) { $currentFileStream.Dispose() } If($null -ne $srcStream) { $srcStream.Dispose() } } } } } process { switch($PSCmdlet.ParameterSetName) { "Path" { ProcessFileContent $Path $false } "LiteralPath" { ProcessFileContent $LiteralPath $true } "ByInputObject" { # If it's an actual byte array, then we directly use it for hexadecimal formatting. if($InputObject -is [Byte[]]) { ConvertToHexadecimalHelper $InputObject $null return } # if it's a single byte, we'll assume streaming elseif($InputObject -is [byte]) { $null = $inputStreamArray.Add($InputObject) } # If the input data is of string type then directly get bytes out of it. elseif($InputObject -is [string]) { # The ValidateSet arribute on the Encoding paramter makes sure that only # valid values (supported on all paltforms where Format-Hex is avaliable) # are allowed through user input. $inputBytes = [Text.Encoding]::$Encoding.GetBytes($InputObject) ConvertToHexadecimalHelper $inputBytes $null return } elseif($InputObject -is [System.IO.FileSystemInfo]) { # If file path is provided as an input, use the file contents to show the hexadecimal format. $filePath = ([System.IO.FileSystemInfo]$InputObject).FullName ProcessFileContent $filePath $false return } elseif($InputObject -is [int64]) { $inputBytes = [BitConverter]::GetBytes($InputObject) $null = $inputStreamArray.AddRange($inputBytes) } elseif($InputObject -is [int64[]]) { foreach($i64 in $InputObject) { $inputBytes = [BitConverter]::GetBytes($i64) $null = $inputStreamArray.AddRange($inputBytes) } } elseif($InputObject -is [int]) { # If we get what appears as ints, it may not be what the user really wants. # for example, if the user types a small set of numbers just to get their # character representations, as follows: # # 170..180 | format-hex # Path: # 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F #00000000 AA AB AC AD AE AF B0 B1 B2 B3 B4 ª«¬­®¯°±²³´ # # any integer padding is likely to be more confusing than this # fairly compact representation. # # However, some might like to see the results with the raw data, # -Raw exists to provide that behavior: # PS# 170..180 | format-hex -Raw # # Path: # # 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F # # 00000000 AA 00 00 00 AB 00 00 00 AC 00 00 00 AD 00 00 00 ª...«...¬...­... # 00000010 AE 00 00 00 AF 00 00 00 B0 00 00 00 B1 00 00 00 ®...¯...°...±... # 00000020 B2 00 00 00 B3 00 00 00 B4 00 00 00 ²...³...´... # # this provides a representation of the piped numbers which includes all # of the bytes which are in an int32 if ( $Raw ) { $inputBytes = [BitConverter]::GetBytes($InputObject) $null = $inputStreamArray.AddRange($inputBytes) } else { # first determine whether we can represent this as a byte $possibleByte = $InputObject -as [byte] # first determine whether we can represent this as a int16 $possibleInt16 = $InputObject -as [int16] if ( $possibleByte -ne $null ) { $null = $inputStreamArray.Add($possibleByte) } elseif ( $possibleint16 -ne $null ) { $inputBytes = [BitConverter]::GetBytes($possibleInt16) $null = $inputStreamArray.AddRange($inputBytes) } else { # now int $inputBytes = [BitConverter]::GetBytes($InputObject) $null = $inputStreamArray.AddRange($inputBytes) } } } else { # Otherwise, write a non-terminating error message indicating that input object type is not supported. $errorMessage = [Microsoft.PowerShell.Commands.UtilityResources]::FormatHexTypeNotSupported -f $InputObject.GetType() Write-Error -Message $errorMessage -Category ([System.Management.Automation.ErrorCategory]::ParserError) -ErrorId "FormatHexFailureTypeNotSupported" } # Handle streaming case here # during this process we may not have enough characters to create a ByteCollection # if we do, create as many ByteCollections as necessary, each being 16 bytes in length if ( $inputStreamArray.Count -ge $bufferSize ) { $rowCount = [math]::Floor($inputStreamArray.Count/$bufferSize) $arrayLength = $bufferSize * $rowCount for($i = 0; $i -lt $rowCount; $i++) { $rowOffset = $i * $bufferSize ConvertToHexadecimalHelper -inputBytes $inputStreamArray.GetRange($rowOffset, $bufferSize) -path ' ' -offset $offset $offset += $bufferSize } # We use RemoveRange because of the pathological case of having # streamed combination of bytes, int16, int32, int64 which are greater # than 16 bytes. Consider the case: # $i = [int16]::MaxValue + 3 # $i64=[int64]::MaxValue -5 # .{ $i;$i;$i;$i64 } | format-hex # which create an arraylist 20 bytes # we need to remove only the bytes from the array which we emitted $inputStreamArray.RemoveRange(0,$arrayLength) } } } } end { # now manage any left over bytes in the $inputStreamArray if ( $PSCmdlet.ParameterSetName -eq "ByInputObject" ) { ConvertToHexadecimalHelper $inputStreamArray $null -path ' ' -offset $offset } } } ## Imports a PowerShell Data File - a PowerShell hashtable defined in ## a file (such as a Module manifest, session configuration file) function Import-PowerShellDataFile { [CmdletBinding(DefaultParameterSetName = "ByPath", HelpUri = "https://go.microsoft.com/fwlink/?LinkID=623621")] [OutputType("System.Collections.Hashtable")] param( [Parameter(ParameterSetName = "ByPath", Position = 0)] [String[]] $Path, [Parameter(ParameterSetName = "ByLiteralPath", ValueFromPipelineByPropertyName = $true)] [Alias("PSPath")] [String[]] $LiteralPath ) begin { function ThrowInvalidDataFile { param($resolvedPath, $extraError) $errorId = "CouldNotParseAsPowerShellDataFile$extraError" $errorCategory = [System.Management.Automation.ErrorCategory]::InvalidData $errorMessage = [Microsoft.PowerShell.Commands.UtilityResources]::CouldNotParseAsPowerShellDataFile -f $resolvedPath $exception = [System.InvalidOperationException]::New($errorMessage) $errorRecord = [System.Management.Automation.ErrorRecord]::New($exception, $errorId, $errorCategory, $null) $PSCmdlet.WriteError($errorRecord) } } process { foreach($resolvedPath in (Resolve-Path @PSBoundParameters)) { $parseErrors = $null $ast = [System.Management.Automation.Language.Parser]::ParseFile(($resolvedPath.ProviderPath), [ref] $null, [ref] $parseErrors) if ($parseErrors.Length -gt 0) { ThrowInvalidDataFile $resolvedPath } else { $data = $ast.Find( { $args[0] -is [System.Management.Automation.Language.HashtableAst] }, $false ) if($data) { $data.SafeGetValue() } else { ThrowInvalidDataFile $resolvedPath "NoHashtableRoot" } } } } } ## Converts a SDDL string into an object-based representation of a security ## descriptor function ConvertFrom-SddlString { [CmdletBinding(HelpUri = "https://go.microsoft.com/fwlink/?LinkId=623636")] param( ## The string representing the security descriptor in SDDL syntax [Parameter(Mandatory = $true, Position = 0, ValueFromPipeline = $true)] [String] $Sddl, ## The type of rights that this SDDL string represents, if any. [Parameter()] [ValidateSet( "FileSystemRights", "RegistryRights", "ActiveDirectoryRights", "MutexRights", "SemaphoreRights", "CryptoKeyRights", "EventWaitHandleRights")] $Type ) Begin { # On CoreCLR CryptoKeyRights and ActiveDirectoryRights are not supported. if ($PSEdition -eq "Core" -and ($Type -eq "CryptoKeyRights" -or $Type -eq "ActiveDirectoryRights")) { $errorId = "TypeNotSupported" $errorCategory = [System.Management.Automation.ErrorCategory]::InvalidArgument $errorMessage = [Microsoft.PowerShell.Commands.UtilityResources]::TypeNotSupported -f $Type $exception = [System.ArgumentException]::New($errorMessage) $errorRecord = [System.Management.Automation.ErrorRecord]::New($exception, $errorId, $errorCategory, $null) $PSCmdlet.ThrowTerminatingError($errorRecord) } ## Translates a SID into a NT Account function ConvertTo-NtAccount { param($Sid) if($Sid) { $securityIdentifier = [System.Security.Principal.SecurityIdentifier] $Sid try { $ntAccount = $securityIdentifier.Translate([System.Security.Principal.NTAccount]).ToString() } catch{} $ntAccount } } ## Gets the access rights that apply to an access mask, preferring right types ## of 'Type' if specified. function Get-AccessRights { param($AccessMask, $Type) if ($PSEdition -eq "Core") { ## All the types of access rights understood by .NET Core $rightTypes = [Ordered] @{ "FileSystemRights" = [System.Security.AccessControl.FileSystemRights] "RegistryRights" = [System.Security.AccessControl.RegistryRights] "MutexRights" = [System.Security.AccessControl.MutexRights] "SemaphoreRights" = [System.Security.AccessControl.SemaphoreRights] "EventWaitHandleRights" = [System.Security.AccessControl.EventWaitHandleRights] } } else { ## All the types of access rights understood by .NET $rightTypes = [Ordered] @{ "FileSystemRights" = [System.Security.AccessControl.FileSystemRights] "RegistryRights" = [System.Security.AccessControl.RegistryRights] "ActiveDirectoryRights" = [System.DirectoryServices.ActiveDirectoryRights] "MutexRights" = [System.Security.AccessControl.MutexRights] "SemaphoreRights" = [System.Security.AccessControl.SemaphoreRights] "CryptoKeyRights" = [System.Security.AccessControl.CryptoKeyRights] "EventWaitHandleRights" = [System.Security.AccessControl.EventWaitHandleRights] } } $typesToExamine = $rightTypes.Values ## If they know the access mask represents a certain type, prefer its names ## (i.e.: CreateLink for the registry over CreateDirectories for the filesystem) if($Type) { $typesToExamine = @($rightTypes[$Type]) + $typesToExamine } ## Stores the access types we've found that apply $foundAccess = @() ## Store the access types we've already seen, so that we don't report access ## flags that are essentially duplicate. Many of the access values in the different ## enumerations have the same value but with different names. $foundValues = @{} ## Go through the entries in the different right types, and see if they apply to the ## provided access mask. If they do, then add that to the result. foreach($rightType in $typesToExamine) { foreach($accessFlag in [Enum]::GetNames($rightType)) { $longKeyValue = [long] $rightType::$accessFlag if(-not $foundValues.ContainsKey($longKeyValue)) { $foundValues[$longKeyValue] = $true if(($AccessMask -band $longKeyValue) -eq ($longKeyValue)) { $foundAccess += $accessFlag } } } } $foundAccess | Sort-Object } ## Converts an ACE into a string representation function ConvertTo-AceString { param( [Parameter(ValueFromPipeline)] $Ace, $Type ) process { foreach($aceEntry in $Ace) { $AceString = (ConvertTo-NtAccount $aceEntry.SecurityIdentifier) + ": " + $aceEntry.AceQualifier if($aceEntry.AceFlags -ne "None") { $AceString += " " + $aceEntry.AceFlags } if($aceEntry.AccessMask) { $foundAccess = Get-AccessRights $aceEntry.AccessMask $Type if($foundAccess) { $AceString += " ({0})" -f ($foundAccess -join ", ") } } $AceString } } } } Process { $rawSecurityDescriptor = [Security.AccessControl.CommonSecurityDescriptor]::new($false,$false,$Sddl) $owner = ConvertTo-NtAccount $rawSecurityDescriptor.Owner $group = ConvertTo-NtAccount $rawSecurityDescriptor.Group $discretionaryAcl = ConvertTo-AceString $rawSecurityDescriptor.DiscretionaryAcl $Type $systemAcl = ConvertTo-AceString $rawSecurityDescriptor.SystemAcl $Type [PSCustomObject] @{ Owner = $owner Group = $group DiscretionaryAcl = @($discretionaryAcl) SystemAcl = @($systemAcl) RawDescriptor = $rawSecurityDescriptor } } }