diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/FormatAndOutput/format-hex/Format-Hex.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/FormatAndOutput/format-hex/Format-Hex.cs index 7cab758287e..595161f7e46 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/FormatAndOutput/format-hex/Format-Hex.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/FormatAndOutput/format-hex/Format-Hex.cs @@ -2,12 +2,14 @@ // Licensed under the MIT License. using System; +using System.Collections.Generic; using System.IO; -using System.Text; -using System.Security; using System.Management.Automation; -using System.Collections.Generic; using System.Management.Automation.Internal; +using System.Reflection; +using System.Runtime.InteropServices; +using System.Security; +using System.Text; // Once Serialization is available on CoreCLR: using System.Runtime.Serialization.Formatters.Binary; namespace Microsoft.PowerShell.Commands @@ -18,38 +20,55 @@ namespace Microsoft.PowerShell.Commands [Cmdlet(VerbsCommon.Format, "Hex", HelpUri = "https://go.microsoft.com/fwlink/?LinkId=526919")] [OutputType(typeof(Microsoft.PowerShell.Commands.ByteCollection))] [Alias("fhx")] - public sealed class FormatHex : PSCmdlet + public sealed class FormatHexCommand : PSCmdlet { private const int BUFFERSIZE = 16; + /// + /// For cases where a homogenous collection of bytes or other items are directly piped in, we collect all the + /// bytes in a List<byte> and then output the formatted result all at once in EndProcessing(). + /// + private List _inputBytes; + + /// + /// If the input is determined to be heterogenous piped input or each input object turns out to be a complete + /// array of items, we output each item as we receive it to avoid squashing output together in strange ways. + /// + private bool _isHeterogenousPipedInput = false; + + /// + /// Keep track of prior input types to determine if we're given a heterogenous collection. + /// + private Type _lastInputType; + #region Parameters /// - /// Path of file(s) to process. + /// Gets or sets the path of file(s) to process. /// [Parameter(Mandatory = true, Position = 0, ParameterSetName = "Path")] - [ValidateNotNullOrEmpty()] + [ValidateNotNullOrEmpty] public string[] Path { get; set; } /// - /// Literal path of file to process. + /// Gets or sets the literal path of file to process. /// - [Parameter(Mandatory = true, ParameterSetName = "LiteralPath")] - [ValidateNotNullOrEmpty()] - [Alias("PSPath", "LP")] + [Parameter(Mandatory = true, ParameterSetName = "LiteralPath", ValueFromPipelineByPropertyName = true)] + [ValidateNotNullOrEmpty] + [Alias("PSPath", "LP", "FullName")] public string[] LiteralPath { get; set; } /// - /// Object to process. + /// Gets or sets the pipeline object to process. /// [Parameter(Mandatory = true, ParameterSetName = "ByInputObject", ValueFromPipeline = true)] public PSObject InputObject { get; set; } /// - /// Type of character encoding for InputObject. + /// Gets or sets the type of character encoding for InputObject. /// [Parameter(ParameterSetName = "ByInputObject")] - [ArgumentToEncodingTransformationAttribute()] + [ArgumentToEncodingTransformationAttribute] [ArgumentEncodingCompletionsAttribute] [ValidateNotNullOrEmpty] public Encoding Encoding { get; set; } = ClrFacade.GetDefaultEncoding(); @@ -59,17 +78,17 @@ public sealed class FormatHex : PSCmdlet /// [Parameter] [ValidateRange(ValidateRangeKind.Positive)] - public Int64 Count { get; set; } = Int64.MaxValue; + public long Count { get; set; } = long.MaxValue; /// /// Gets or sets offset of bytes to start reading the input stream from. /// [Parameter] [ValidateRange(ValidateRangeKind.NonNegative)] - public Int64 Offset { get; set; } + public long Offset { get; set; } /// - /// This parameter is no-op. + /// Gets or sets whether the file input should be swallowed as is. This parameter is no-op, deprecated. /// [Parameter(ParameterSetName = "ByInputObject", DontShow = true)] [Obsolete("Raw parameter is deprecated.", true)] @@ -97,6 +116,26 @@ protected override void ProcessRecord() } } + /// + /// Implements the EndProcessing method for the FormatHex command. + /// + protected override void EndProcessing() + { + if (_inputBytes != null) + { + int offset = Math.Min(_inputBytes.Count, Offset < (long)int.MaxValue ? (int)Offset : int.MaxValue); + int count = Math.Min(_inputBytes.Count - offset, Count < (long)int.MaxValue ? (int)Count : int.MaxValue); + if (offset != 0 || count != _inputBytes.Count) + { + WriteHexadecimal(_inputBytes.GetRange(offset, count).ToArray(), null, 0); + } + else + { + WriteHexadecimal(_inputBytes.ToArray(), null, 0); + } + } + } + #endregion #region Paths @@ -106,9 +145,9 @@ protected override void ProcessRecord() /// If path is a literal path it is added to the array to process; we cannot validate them until we /// try to process file contents. /// - /// - /// - /// + /// The file path to resolve. + /// Indicates whether the path is to be resolved as literal or may have wildcards. + /// Returns a list of resolved paths. private List ResolvePaths(string[] path, bool literalPath) { List pathsToProcess = new List(); @@ -144,10 +183,11 @@ private List ResolvePaths(string[] path, bool literalPath) { // Write a non-terminating error message indicating that path specified is not supported. string errorMessage = StringUtil.Format(UtilityCommonStrings.FormatHexOnlySupportsFileSystemPaths, currentPath); - ErrorRecord errorRecord = new ErrorRecord(new ArgumentException(errorMessage), - "FormatHexOnlySupportsFileSystemPaths", - ErrorCategory.InvalidArgument, - currentPath); + ErrorRecord errorRecord = new ErrorRecord( + new ArgumentException(errorMessage), + "FormatHexOnlySupportsFileSystemPaths", + ErrorCategory.InvalidArgument, + currentPath); WriteError(errorRecord); continue; } @@ -161,7 +201,7 @@ private List ResolvePaths(string[] path, bool literalPath) /// /// Pass each valid path on to process its contents. /// - /// + /// The paths to process. private void ProcessPath(List pathsToProcess) { foreach (string path in pathsToProcess) @@ -174,7 +214,7 @@ private void ProcessPath(List pathsToProcess) /// Creates a binary reader that reads the file content into a buffer (byte[]) 16 bytes at a time, and /// passes a copy of that array on to the WriteHexidecimal method to output. /// - /// + /// The file path to retrieve content from for processing. private void ProcessFileContent(string path) { Span buffer = stackalloc byte[BUFFERSIZE]; @@ -183,9 +223,9 @@ private void ProcessFileContent(string path) { using (BinaryReader reader = new BinaryReader(File.Open(path, FileMode.Open, FileAccess.Read, FileShare.Read))) { - Int64 offset = Offset; - Int32 bytesRead = 0; - Int64 count = 0; + long offset = Offset; + int bytesRead = 0; + long count = 0; reader.BaseStream.Position = Offset; @@ -195,20 +235,20 @@ private void ProcessFileContent(string path) if (count > Count) { bytesRead -= (int)(count - Count); - WriteHexidecimal(buffer.Slice(0, bytesRead), path, offset); + WriteHexadecimal(buffer.Slice(0, bytesRead), path, offset); break; } - WriteHexidecimal(buffer.Slice(0, bytesRead), path, offset); + WriteHexadecimal(buffer.Slice(0, bytesRead), path, offset); offset += bytesRead; } } } - catch (IOException ioException) + catch (IOException fileException) { // IOException takes care of FileNotFoundException, DirectoryNotFoundException, and PathTooLongException - WriteError(new ErrorRecord(ioException, "FormatHexIOError", ErrorCategory.WriteError, path)); + WriteError(new ErrorRecord(fileException, "FormatHexIOError", ErrorCategory.WriteError, path)); } catch (ArgumentException argException) { @@ -232,84 +272,188 @@ private void ProcessFileContent(string path) /// Creates a byte array from the object passed to the cmdlet (based on type) and passes /// that array on to the WriteHexidecimal method to output. /// - /// + /// The pipeline input object being processed. private void ProcessObjectContent(PSObject inputObject) { - object obj = inputObject.BaseObject; - byte[] inputBytes = null; + dynamic baseObject = inputObject.BaseObject; + Type baseType = baseObject.GetType(); + int elements = 1; + bool isArray = false; + bool isBool = false; + bool isEnum = false; + + byte[] processResult = null; + if (baseType.IsArray) + { + baseType = baseType.GetElementType(); + elements = (int)baseObject.Length; + isArray = true; + _isHeterogenousPipedInput = true; + } - switch (obj) + if (baseType == typeof(FileInfo)) { - case System.IO.FileSystemInfo fsi: - string[] path = { fsi.FullName }; - List pathsToProcess = ResolvePaths(path, true); - ProcessPath(pathsToProcess); - return; - case string str: - inputBytes = Encoding.GetBytes(str); - break; - case byte b: - inputBytes = new byte[] { b }; - break; - case byte[] byteArray: - inputBytes = byteArray; - break; - case Int32 iInt32: - inputBytes = BitConverter.GetBytes(iInt32); - break; - case Int32[] i32s: - int i32 = 0; - inputBytes = new byte[sizeof(Int32) * i32s.Length]; - Span inputStreamArray32 = inputBytes; - - foreach (Int32 value in i32s) + List paths = new List(); + if (!isArray) + { + paths.Add(baseObject.FullName); + } + else + { + foreach (FileInfo file in baseObject) { - BitConverter.TryWriteBytes(inputStreamArray32.Slice(i32), value); - i32 += sizeof(Int32); + paths.Add(file.FullName); } + } - break; - case Int64 iInt64: - inputBytes = BitConverter.GetBytes(iInt64); - break; - case Int64[] inputInt64s: - int i64 = 0; - inputBytes = new byte[sizeof(Int64) * inputInt64s.Length]; - Span inputStreamArray64 = inputBytes; + List pathsToProcess = new List(ResolvePaths(paths.ToArray(), true)); + ProcessPath(pathsToProcess); + return; + } - foreach (Int64 value in inputInt64s) + if (baseType == typeof(string)) + { + _isHeterogenousPipedInput = true; + + if (!isArray) + { + baseObject = new string[] { baseObject }; + } + + foreach (string str in (Array)baseObject) + { + processResult = Encoding.GetBytes(str); + int offset = Math.Min(processResult.Length, Offset < (long)int.MaxValue ? (int)Offset : int.MaxValue); + int count = Math.Min(processResult.Length - offset, Count < (long)int.MaxValue ? (int)Count : int.MaxValue); + if (offset != 0 || count != processResult.Length) { - BitConverter.TryWriteBytes(inputStreamArray64.Slice(i64), value); - i64 += sizeof(Int64); + WriteHexadecimal(processResult.AsSpan().Slice(offset, count), null, 0); } + else + { + WriteHexadecimal(processResult, null, 0); + } + } - break; + return; + } - // If the object type is not supported, throw an error. Once Serialization is - // available on CoreCLR, other types will be supported. - default: + if (baseType.IsEnum) + { + baseType = baseType.GetEnumUnderlyingType(); + isEnum = true; + } + + if (!_isHeterogenousPipedInput) + { + if (_lastInputType != null && baseType != _lastInputType) { - string errorMessage = StringUtil.Format(UtilityCommonStrings.FormatHexTypeNotSupported, obj.GetType()); - ErrorRecord errorRecord = new ErrorRecord(new ArgumentException(errorMessage), - "FormatHexTypeNotSupported", - ErrorCategory.InvalidArgument, - obj.GetType()); - WriteError(errorRecord); - break; + _isHeterogenousPipedInput = true; + } + else + { + _lastInputType = baseType; } } - if (inputBytes != null) + if (baseType.IsPrimitive && elements > 0) + { + if (baseType == typeof(bool)) + { + isBool = true; + } + + var elementSize = Marshal.SizeOf(baseType); + processResult = new byte[elementSize * elements]; + if (!isArray) + { + baseObject = new object[1] { baseObject }; + } + + int index = 0; + foreach (dynamic item in (Array)baseObject) + { + if (elementSize == 1) + { + processResult[index] = (byte)item; + } + else + { + // bool is 4 bytes, apparently -- @lzybkr + dynamic byteConverterInput; + if (isEnum) + { + byteConverterInput = Convert.ChangeType(item, baseType); + } + else if (isBool) + { + byteConverterInput = Convert.ToInt32(item); + } + else + { + byteConverterInput = item; + } + + byte[] bytes = BitConverter.GetBytes(byteConverterInput); + for (int i = 0; i < bytes.Length; i++) + { + processResult[i + index] = bytes[i]; + } + } + + index += elementSize; + } + } + else + { + // Type is neither any kind of primitive, enum, string, nor file, so we write an error + string errorMessage = StringUtil.Format(UtilityCommonStrings.FormatHexTypeNotSupported, baseObject.GetType()); + ErrorRecord errorRecord = new ErrorRecord( + new ArgumentException(errorMessage), + "FormatHexTypeNotSupported", + ErrorCategory.InvalidArgument, + baseObject.GetType()); + WriteError(errorRecord); + return; + } + + if (_isHeterogenousPipedInput) + { + if (_inputBytes != null) + { + // If we've been collecting individual bytes now, and some other input has been detected, + // we revert to heterogenous behaviour + foreach (byte b in _inputBytes) + { + WriteHexadecimal(new byte[] { b }, null, 0); + } + + _inputBytes = null; + } + + if (processResult != null) + { + int offset = Math.Min(processResult.Length, Offset < (long)int.MaxValue ? (int)Offset : int.MaxValue); + int count = Math.Min(processResult.Length - offset, Count < (long)int.MaxValue ? (int)Count : int.MaxValue); + if (offset != 0 || count != processResult.Length) + { + WriteHexadecimal(processResult.AsSpan().Slice(offset, count), null, 0); + } + else + { + WriteHexadecimal(processResult, null, 0); + } + } + } + else { - int offset = Math.Min(inputBytes.Length, Offset < (long)int.MaxValue ? (int)Offset : int.MaxValue); - int count = Math.Min(inputBytes.Length - offset, Count < (long)int.MaxValue ? (int)Count : int.MaxValue); - if (offset != 0 || count != inputBytes.Length) + if (_inputBytes == null) { - WriteHexidecimal(inputBytes.AsSpan().Slice(offset, count), null, 0); + _inputBytes = new List(processResult); } else { - WriteHexidecimal(inputBytes, null, 0); + _inputBytes.AddRange(processResult); } } } @@ -324,15 +468,15 @@ private void ProcessObjectContent(PSObject inputObject) /// Bytes for the hexadecimial representaion. /// File path. /// Offset in the file. - private void WriteHexidecimal(Span inputBytes, string path, Int64 offset) + private void WriteHexadecimal(Span inputBytes, string path, long offset) { - ByteCollection byteCollectionObject = new ByteCollection((UInt64)offset, inputBytes.ToArray(), path); + ByteCollection byteCollectionObject = new ByteCollection((ulong)offset, inputBytes.ToArray(), path); WriteObject(byteCollectionObject); } - private void WriteHexidecimal(byte[] inputBytes, string path, Int64 offset) + private void WriteHexadecimal(byte[] inputBytes, string path, long offset) { - ByteCollection byteCollectionObject = new ByteCollection((UInt64)offset, inputBytes, path); + ByteCollection byteCollectionObject = new ByteCollection((ulong)offset, inputBytes, path); WriteObject(byteCollectionObject); } diff --git a/test/powershell/Modules/Microsoft.PowerShell.Utility/FormatHex.Tests.ps1 b/test/powershell/Modules/Microsoft.PowerShell.Utility/FormatHex.Tests.ps1 index 9dd90a125cf..12c6cc1c910 100644 --- a/test/powershell/Modules/Microsoft.PowerShell.Utility/FormatHex.Tests.ps1 +++ b/test/powershell/Modules/Microsoft.PowerShell.Utility/FormatHex.Tests.ps1 @@ -34,8 +34,7 @@ Describe "FormatHex" -tags "CI" { $thumbprint = $null $certProviderAvailable = $false - if ($certificateProvider.Count -gt 0) - { + if ($certificateProvider.Count -gt 0) { $thumbprint = $certificateProvider[0].Thumbprint $certProviderAvailable = $true } @@ -44,64 +43,90 @@ Describe "FormatHex" -tags "CI" { } Context "InputObject Paramater" { + BeforeAll { + enum TestEnum { + TestOne = 1; TestTwo = 2; TestThree = 3; TestFour = 4 + } + Add-Type -TypeDefinition @' +public enum TestSByteEnum : sbyte { + One = -1, + Two = -2, + Three = -3, + Four = -4 +} +'@ + } + $testCases = @( @{ - Name = "Can process byte type 'fhx -InputObject [byte]5'" - InputObject = [byte]5 - Count = 1 + Name = "Can process byte type 'fhx -InputObject [byte]5'" + InputObject = [byte]5 + Count = 1 ExpectedResult = "00000000 05" } @{ - Name = "Can process byte[] type 'fhx -InputObject [byte[]](1,2,3,4,5)'" - InputObject = [byte[]](1,2,3,4,5) - Count = 1 + Name = "Can process byte[] type 'fhx -InputObject [byte[]](1,2,3,4,5)'" + InputObject = [byte[]](1, 2, 3, 4, 5) + Count = 1 ExpectedResult = "00000000 01 02 03 04 05 ....." } @{ - Name = "Can process int type 'fhx -InputObject 7'" - InputObject = 7 - Count = 1 + Name = "Can process int type 'fhx -InputObject 7'" + InputObject = 7 + Count = 1 ExpectedResult = "00000000 07 00 00 00 ...." } @{ - Name = "Can process int[] type 'fhx -InputObject [int[]](5,6,7,8)'" - InputObject = [int[]](5,6,7,8) - Count = 1 + Name = "Can process int[] type 'fhx -InputObject [int[]](5,6,7,8)'" + InputObject = [int[]](5, 6, 7, 8) + Count = 1 ExpectedResult = "00000000 05 00 00 00 06 00 00 00 07 00 00 00 08 00 00 00 ................" } @{ - Name = "Can process int32 type 'fhx -InputObject [int32]2032'" - InputObject = [int32]2032 - Count = 1 + Name = "Can process int32 type 'fhx -InputObject [int32]2032'" + InputObject = [int32]2032 + Count = 1 ExpectedResult = "00000000 F0 07 00 00 ð..." } @{ - Name = "Can process int32[] type 'fhx -InputObject [int32[]](2032, 2033, 2034)'" - InputObject = [int32[]](2032, 2033, 2034) - Count = 1 + Name = "Can process int32[] type 'fhx -InputObject [int32[]](2032, 2033, 2034)'" + InputObject = [int32[]](2032, 2033, 2034) + Count = 1 ExpectedResult = "00000000000000000000 F0 07 00 00 F1 07 00 00 F2 07 00 00 ð...ñ...ò..." } @{ - Name = "Can process Int64 type 'fhx -InputObject [Int64]9223372036854775807'" - InputObject = [Int64]9223372036854775807 - Count = 1 + Name = "Can process Int64 type 'fhx -InputObject [Int64]9223372036854775807'" + InputObject = [Int64]9223372036854775807 + Count = 1 ExpectedResult = "00000000000000000000 FF FF FF FF FF FF FF 7F ......." } @{ - Name = "Can process Int64[] type 'fhx -InputObject [Int64[]](9223372036852,9223372036853)'" - InputObject = [Int64[]](9223372036852,9223372036853) - Count = 1 + Name = "Can process Int64[] type 'fhx -InputObject [Int64[]](9223372036852,9223372036853)'" + InputObject = [Int64[]](9223372036852, 9223372036853) + Count = 1 ExpectedResult = "00000000000000000000 F4 5A D0 7B 63 08 00 00 F5 5A D0 7B 63 08 00 00 ôZÐ{c...õZÐ{c..." } @{ - Name = "Can process string type 'fhx -InputObject hello world'" - InputObject = "hello world" - Count = 1 + Name = "Can process string type 'fhx -InputObject hello world'" + InputObject = "hello world" + Count = 1 ExpectedResult = "00000000000000000000 68 65 6C 6C 6F 20 77 6F 72 6C 64 hello world" } + @{ + Name = "Can process PS-native enum array '[TestEnum[]]('TestOne', 'TestTwo', 'TestThree', 'TestFour') | fhx'" + InputObject = [TestEnum[]]('TestOne', 'TestTwo', 'TestThree', 'TestFour') + Count = 1 + ExpectedResult = "00000000000000000000 01 00 00 00 02 00 00 00 03 00 00 00 04 00 00 00 ................" + } + @{ + Name = "Can process C#-native sbyte enum array '[TestSByteEnum[]]('One', 'Two', 'Three', 'Four') | fhx'" + InputObject = [TestSByteEnum[]]('One', 'Two', 'Three', 'Four') + Count = 1 + ExpectedResult = "00000000000000000000 FF FE FD FC .þýü" + } ) - It "" -TestCase $testCases{ + It "" -TestCase $testCases { param ($Name, $InputObject, $Count, $ExpectedResult) @@ -114,65 +139,94 @@ Describe "FormatHex" -tags "CI" { } Context "InputObject From Pipeline" { + BeforeAll { + enum TestEnum { + TestOne = 1; TestTwo = 2; TestThree = 3; TestFour = 4 + } + Add-Type -TypeDefinition @' +public enum TestSByteEnum : sbyte { + One = -1, + Two = -2, + Three = -3, + Four = -4 +} +'@ + } + $testCases = @( @{ - Name = "Can process byte type '[byte]5 | fhx'" - InputObject = [byte]5 - Count = 1 + Name = "Can process byte type '[byte]5 | fhx'" + InputObject = [byte]5 + Count = 1 ExpectedResult = "00000000000000000000 05" } @{ - Name = "Can process byte[] type '[byte[]](1,2) | fhx'" - InputObject = [byte[]](1,2) - Count = 2 - ExpectedResult = "00000000000000000000 01 ." - ExpectedSecondResult = "00000000000000000000 02 ." + Name = "Can process byte[] type '[byte[]](1,2) | fhx'" + InputObject = [byte[]](1, 2) + Count = 1 + ExpectedResult = "00000000000000000000 01 02 .." } @{ - Name = "Can process int type '7 | fhx'" - InputObject = 7 - Count = 1 + Name = "Can process int type '7 | fhx'" + InputObject = 7 + Count = 1 ExpectedResult = "00000000000000000000 07 00 00 00 ...." } @{ - Name = "Can process int[] type '[int[]](5,6) | fhx'" - InputObject = [int[]](5,6) - Count = 2 - ExpectedResult = "00000000000000000000 05 00 00 00 ...." - ExpectedSecondResult = "00000000000000000000 06 00 00 00 ...." + Name = "Can process int[] type '[int[]](5,6) | fhx'" + InputObject = [int[]](5, 6) + Count = 1 + ExpectedResult = "00000000000000000000 05 00 00 00 06 00 00 00 ........" } @{ - Name = "Can process int32 type '[int32]2032 | fhx'" - InputObject = [int32]2032 - Count = 1 + Name = "Can process int32 type '[int32]2032 | fhx'" + InputObject = [int32]2032 + Count = 1 ExpectedResult = "00000000000000000000 F0 07 00 00 ð..." } @{ - Name = "Can process int32[] type '[int32[]](2032, 2033) | fhx'" - InputObject = [int32[]](2032, 2033) - Count = 2 - ExpectedResult = "00000000000000000000 F0 07 00 00 ð..." - ExpectedSecondResult = "00000000000000000000 F1 07 00 00 ñ..." + Name = "Can process int32[] type '[int32[]](2032, 2033) | fhx'" + InputObject = [int32[]](2032, 2033) + Count = 1 + ExpectedResult = "00000000000000000000 F0 07 00 00 F1 07 00 00 ð...ñ..." } @{ - Name = "Can process Int64 type '[Int64]9223372036854775807 | fhx'" - InputObject = [Int64]9223372036854775807 - Count = 1 + Name = "Can process Int64 type '[Int64]9223372036854775807 | fhx'" + InputObject = [Int64]9223372036854775807 + Count = 1 ExpectedResult = "00000000000000000000 FF FF FF FF FF FF FF 7F ......." } @{ - Name = "Can process Int64[] type '[Int64[]](9223372036852,9223372036853) | fhx'" - InputObject = [Int64[]](9223372036852,9223372036853) - Count = 2 - ExpectedResult = "00000000000000000000 F4 5A D0 7B 63 08 00 00 ôZÐ{c..." - ExpectedSecondResult = "00000000000000000000 F5 5A D0 7B 63 08 00 00 õZÐ{c..." + Name = "Can process Int64[] type '[Int64[]](9223372036852,9223372036853) | fhx'" + InputObject = [Int64[]](9223372036852, 9223372036853) + Count = 1 + ExpectedResult = "00000000000000000000 F4 5A D0 7B 63 08 00 00 F5 5A D0 7B 63 08 00 00 ôZÐ{c...õZÐ{c..." } @{ - Name = "Can process string type 'hello world | fhx'" - InputObject = "hello world" - Count = 1 + Name = "Can process string type 'hello world | fhx'" + InputObject = "hello world" + Count = 1 ExpectedResult = "00000000000000000000 68 65 6C 6C 6F 20 77 6F 72 6C 64 hello world" } + @{ + Name = "Can process jagged array type '[sbyte[]](-15, 18, 21, -5), [byte[]](1, 2, 3, 4, 5, 6) | fhx'" + InputObject = [sbyte[]](-15, 18, 21, -5), [byte[]](1, 2, 3, 4, 5, 6) + Count = 2 + ExpectedResult = "00000000000000000000 F1 12 15 FB ñ..û" + ExpectedSecondResult = "00000000000000000000 01 02 03 04 05 06 ......" + } + @{ + Name = "Can process PS-native enum array '[TestEnum[]]('TestOne', 'TestTwo', 'TestThree', 'TestFour') | fhx'" + InputObject = [TestEnum[]]('TestOne', 'TestTwo', 'TestThree', 'TestFour') + Count = 1 + ExpectedResult = "00000000000000000000 01 00 00 00 02 00 00 00 03 00 00 00 04 00 00 00 ................" + } + @{ + Name = "Can process C#-native sbyte enum array '[TestSByteEnum[]]('One', 'Two', 'Three', 'Four') | fhx'" + InputObject = [TestSByteEnum[]]('One', 'Two', 'Three', 'Four') + Count = 1 + ExpectedResult = "00000000000000000000 FF FE FD FC .þýü" + } ) It "" -Testcase $testCases { @@ -181,12 +235,11 @@ Describe "FormatHex" -tags "CI" { $result = $InputObject | Format-Hex - $result.count | Should -Be $Count + $result.Count | Should -Be $Count $result | Should -BeOfType 'Microsoft.PowerShell.Commands.ByteCollection' $result[0].ToString() | Should -MatchExactly $ExpectedResult - if ($result.count -gt 1) - { + if ($result.count -gt 1) { $result[1].ToString() | Should -MatchExactly $ExpectedSecondResult } } @@ -198,39 +251,39 @@ Describe "FormatHex" -tags "CI" { $testCases = @( @{ - Name = "Can process file content from given file path 'fhx -Path `$inputFile1'" - PathCase = $true - Path = $inputFile1 - Count = 1 + Name = "Can process file content from given file path 'fhx -Path `$inputFile1'" + PathCase = $true + Path = $inputFile1 + Count = 1 ExpectedResult = $inputText1 } @{ - Name = "Can process file content from all files in array of file paths 'fhx -Path `$inputFile1, `$inputFile2'" - PathCase = $true - Path = @($inputFile1, $inputFile2) - Count = 2 - ExpectedResult = $inputText1 + Name = "Can process file content from all files in array of file paths 'fhx -Path `$inputFile1, `$inputFile2'" + PathCase = $true + Path = @($inputFile1, $inputFile2) + Count = 2 + ExpectedResult = $inputText1 ExpectedSecondResult = $inputText2 } @{ - Name = "Can process file content from all files when resolved to multiple paths 'fhx -Path '`$testDirectory\SourceFile-*''" - PathCase = $true - Path = "$testDirectory\SourceFile-*" - Count = 2 - ExpectedResult = $inputText1 + Name = "Can process file content from all files when resolved to multiple paths 'fhx -Path '`$testDirectory\SourceFile-*''" + PathCase = $true + Path = "$testDirectory\SourceFile-*" + Count = 2 + ExpectedResult = $inputText1 ExpectedSecondResult = $inputText2 } @{ - Name = "Can process file content from given file path 'fhx -LiteralPath `$inputFile3'" - Path = $inputFile3 - Count = 1 + Name = "Can process file content from given file path 'fhx -LiteralPath `$inputFile3'" + Path = $inputFile3 + Count = 1 ExpectedResult = $inputText3 } @{ - Name = "Can process file content from all files in array of file paths 'fhx -LiteralPath `$inputFile1, `$inputFile3'" - Path = @($inputFile1, $inputFile3) - Count = 2 - ExpectedResult = $inputText1 + Name = "Can process file content from all files in array of file paths 'fhx -LiteralPath `$inputFile1, `$inputFile3'" + Path = @($inputFile1, $inputFile3) + Count = 2 + ExpectedResult = $inputText1 ExpectedSecondResult = $inputText3 } ) @@ -239,61 +292,75 @@ Describe "FormatHex" -tags "CI" { param ($Name, $PathCase, $Path, $ExpectedResult, $ExpectedSecondResult) - if ($PathCase) - { - $result = Format-Hex -Path $Path + if ($PathCase) { + $result = Format-Hex -Path $Path } - else # LiteralPath - { + else { + # LiteralPath $result = Format-Hex -LiteralPath $Path } $result | Should -BeOfType 'Microsoft.PowerShell.Commands.ByteCollection' $result[0].ToString() | Should -MatchExactly $ExpectedResult - if ($result.count -gt 1) - { + if ($result.count -gt 1) { $result[1].ToString() | Should -MatchExactly $ExpectedSecondResult } } + + It 'properly accepts -LiteralPath input from a FileInfo object' { + $FilePath = 'TestDrive:\FHX-LitPathTest.txt' + "Hello World!" | Set-Content -Path $FilePath + $FileObject = Get-Item -Path $FilePath + + $result = $FileObject | Format-Hex + if ($IsWindows) { + $ExpectedResult = "00000000000000000000 48 65 6C 6C 6F 20 57 6F 72 6C 64 21 0D 0A Hello World!.." + } + else { + $ExpectedResult = "00000000000000000000 48 65 6C 6C 6F 20 57 6F 72 6C 64 21 0A Hello World!." + } + + $result[0].ToString() | Should -MatchExactly $ExpectedResult + } } Context "Encoding Parameter" { $testCases = @( @{ - Name = "Can process ASCII encoding 'fhx -InputObject 'hello' -Encoding ASCII'" - Encoding = "ASCII" - Count = 1 + Name = "Can process ASCII encoding 'fhx -InputObject 'hello' -Encoding ASCII'" + Encoding = "ASCII" + Count = 1 ExpectedResult = "00000000000000000000 68 65 6C 6C 6F hello" } @{ - Name = "Can process BigEndianUnicode encoding 'fhx -InputObject 'hello' -Encoding BigEndianUnicode'" - Encoding = "BigEndianUnicode" - Count = 1 + Name = "Can process BigEndianUnicode encoding 'fhx -InputObject 'hello' -Encoding BigEndianUnicode'" + Encoding = "BigEndianUnicode" + Count = 1 ExpectedResult = "00000000000000000000 00 68 00 65 00 6C 00 6C 00 6F .h.e.l.l.o" } @{ - Name = "Can process Unicode encoding 'fhx -InputObject 'hello' -Encoding Unicode'" - Encoding = "Unicode" - Count = 1 + Name = "Can process Unicode encoding 'fhx -InputObject 'hello' -Encoding Unicode'" + Encoding = "Unicode" + Count = 1 ExpectedResult = "00000000000000000000 68 00 65 00 6C 00 6C 00 6F 00 h.e.l.l.o." } @{ - Name = "Can process UTF7 encoding 'fhx -InputObject 'hello' -Encoding UTF7'" - Encoding = "UTF7" - Count = 1 + Name = "Can process UTF7 encoding 'fhx -InputObject 'hello' -Encoding UTF7'" + Encoding = "UTF7" + Count = 1 ExpectedResult = "00000000000000000000 68 65 6C 6C 6F hello" } - @{ - Name = "Can process UTF8 encoding 'fhx -InputObject 'hello' -Encoding UTF8'" - Encoding = "UTF8" - Count = 1 + @{ + Name = "Can process UTF8 encoding 'fhx -InputObject 'hello' -Encoding UTF8'" + Encoding = "UTF8" + Count = 1 ExpectedResult = "00000000000000000000 68 65 6C 6C 6F hello" } - @{ - Name = "Can process UTF32 encoding 'fhx -InputObject 'hello' -Encoding UTF32'" - Encoding = "UTF32" - Count = 1 + @{ + Name = "Can process UTF32 encoding 'fhx -InputObject 'hello' -Encoding UTF32'" + Encoding = "UTF32" + Count = 1 ExpectedResult = "00000000000000000000 68 00 00 00 65 00 00 00 6C 00 00 00 6C 00 00 00 h...e...l...l...$($newline)00000000000000000010 6F 00 00 00 o..." } ) @@ -316,16 +383,16 @@ Describe "FormatHex" -tags "CI" { $testCases = @( @{ - Name = "Does not support non-FileSystem Provider paths 'fhx -Path 'Cert:\CurrentUser\My\`$thumbprint' -ErrorAction Stop'" - PathParameterErrorCase = $true - Path = "Cert:\CurrentUser\My\$thumbprint" + Name = "Does not support non-FileSystem Provider paths 'fhx -Path 'Cert:\CurrentUser\My\`$thumbprint' -ErrorAction Stop'" + PathParameterErrorCase = $true + Path = "Cert:\CurrentUser\My\$thumbprint" ExpectedFullyQualifiedErrorId = "FormatHexOnlySupportsFileSystemPaths,Microsoft.PowerShell.Commands.FormatHex" } @{ - Name = "Type Not Supported 'fhx -InputObject @{'hash' = 'table'} -ErrorAction Stop'" - InputObjectErrorCase = $true - Path = $inputFile1 - InputObject = @{ "hash" = "table" } + Name = "Type Not Supported 'fhx -InputObject @{'hash' = 'table'} -ErrorAction Stop'" + InputObjectErrorCase = $true + Path = $inputFile1 + InputObject = @{ "hash" = "table" } ExpectedFullyQualifiedErrorId = "FormatHexTypeNotSupported,Microsoft.PowerShell.Commands.FormatHex" } ) @@ -335,12 +402,10 @@ Describe "FormatHex" -tags "CI" { param ($Name, $PathParameterErrorCase, $Path, $InputObject, $InputObjectErrorCase, $ExpectedFullyQualifiedErrorId) { - if ($PathParameterErrorCase) - { + if ($PathParameterErrorCase) { $result = Format-Hex -Path $Path -ErrorAction Stop } - if ($InputObjectErrorCase) - { + if ($InputObjectErrorCase) { $result = Format-Hex -InputObject $InputObject -ErrorAction Stop } } | Should -Throw -ErrorId $ExpectedFullyQualifiedErrorId @@ -351,20 +416,20 @@ Describe "FormatHex" -tags "CI" { $testCases = @( @{ - Name = "If given invalid path in array, continues to process valid paths 'fhx -Path `$invalidPath, `$inputFile1 -ErrorVariable e -ErrorAction SilentlyContinue'" - PathCase = $true - InvalidPath = "$($inputFile1.DirectoryName)\fakefile8888845345345348709.txt" + Name = "If given invalid path in array, continues to process valid paths 'fhx -Path `$invalidPath, `$inputFile1 -ErrorVariable e -ErrorAction SilentlyContinue'" + PathCase = $true + InvalidPath = "$($inputFile1.DirectoryName)\fakefile8888845345345348709.txt" ExpectedFullyQualifiedErrorId = "FileNotFound,Microsoft.PowerShell.Commands.FormatHex" } @{ - Name = "If given a non FileSystem path in array, continues to process valid paths 'fhx -Path `$invalidPath, `$inputFile1 -ErrorVariable e -ErrorAction SilentlyContinue'" - PathCase = $true - InvalidPath = "Cert:\CurrentUser\My\$thumbprint" + Name = "If given a non FileSystem path in array, continues to process valid paths 'fhx -Path `$invalidPath, `$inputFile1 -ErrorVariable e -ErrorAction SilentlyContinue'" + PathCase = $true + InvalidPath = "Cert:\CurrentUser\My\$thumbprint" ExpectedFullyQualifiedErrorId = "FormatHexOnlySupportsFileSystemPaths,Microsoft.PowerShell.Commands.FormatHex" } @{ - Name = "If given a non FileSystem path in array (with LiteralPath), continues to process valid paths 'fhx -Path `$invalidPath, `$inputFile1 -ErrorVariable e -ErrorAction SilentlyContinue'" - InvalidPath = "Cert:\CurrentUser\My\$thumbprint" + Name = "If given a non FileSystem path in array (with LiteralPath), continues to process valid paths 'fhx -Path `$invalidPath, `$inputFile1 -ErrorVariable e -ErrorAction SilentlyContinue'" + InvalidPath = "Cert:\CurrentUser\My\$thumbprint" ExpectedFullyQualifiedErrorId = "FormatHexOnlySupportsFileSystemPaths,Microsoft.PowerShell.Commands.FormatHex" } ) @@ -376,12 +441,11 @@ Describe "FormatHex" -tags "CI" { $output = $null $errorThrown = $null - if ($PathCase) - { + if ($PathCase) { $output = Format-Hex -Path $InvalidPath, $inputFile1 -ErrorVariable errorThrown -ErrorAction SilentlyContinue } - else # LiteralPath - { + else { + # LiteralPath $output = Format-Hex -LiteralPath $InvalidPath, $inputFile1 -ErrorVariable errorThrown -ErrorAction SilentlyContinue } @@ -396,10 +460,10 @@ Describe "FormatHex" -tags "CI" { It "Path is default Parameter Set 'fhx `$inputFile1'" { - $result = Format-Hex $inputFile1 + $result = Format-Hex $inputFile1 $result | Should -Not -BeNullOrEmpty - ,$result | Should -BeOfType 'Microsoft.PowerShell.Commands.ByteCollection' + , $result | Should -BeOfType 'Microsoft.PowerShell.Commands.ByteCollection' $actualResult = $result.ToString() $actualResult | Should -MatchExactly $inputText1 } @@ -409,7 +473,7 @@ Describe "FormatHex" -tags "CI" { $result = Get-ChildItem $inputFile1 | Format-Hex $result | Should -Not -BeNullOrEmpty - ,$result | Should -BeOfType 'Microsoft.PowerShell.Commands.ByteCollection' + , $result | Should -BeOfType 'Microsoft.PowerShell.Commands.ByteCollection' $actualResult = $result.ToString() $actualResult | Should -MatchExactly $inputText1 } @@ -419,7 +483,7 @@ Describe "FormatHex" -tags "CI" { $result = "a" * 30 | Format-Hex $result | Should -Not -BeNullOrEmpty - ,$result | Should -BeOfType 'Microsoft.PowerShell.Commands.ByteCollection' + , $result | Should -BeOfType 'Microsoft.PowerShell.Commands.ByteCollection' $result.ToString() | Should -MatchExactly "00000000000000000000 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 aaaaaaaaaaaaaaaa$($newline)00000000000000000010 61 61 61 61 61 61 61 61 61 61 61 61 61 61 aaaaaaaaaaaaaa " }