-
Notifications
You must be signed in to change notification settings - Fork 8.3k
Format-Hex: Improve mixed-collection piped input and piped streams of input #8674
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
186b3b2
caa70c6
6e74e26
09da3d1
eeac89c
94d5c3d
f2a90a8
3265993
045ebd6
2212508
de428f7
3956925
af5f72b
2ff9958
b2ff7c7
89656bd
3c53956
77caa05
3d8955c
116f096
d3563a4
06793e7
643bdcf
65f9cbe
61722d2
f78bfa9
e12caad
feb5d57
a2ae168
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -17,12 +17,30 @@ namespace Microsoft.PowerShell.Commands | |
| /// Displays the hexadecimal equivalent of the input data. | ||
|
vexx32 marked this conversation as resolved.
Outdated
vexx32 marked this conversation as resolved.
TravisEz13 marked this conversation as resolved.
|
||
| /// </summary> | ||
| [Cmdlet(VerbsCommon.Format, "Hex", HelpUri = "https://go.microsoft.com/fwlink/?LinkId=526919")] | ||
| [OutputType(typeof(Microsoft.PowerShell.Commands.ByteCollection))] | ||
| [OutputType(typeof(ByteCollection))] | ||
| [Alias("fhx")] | ||
| public sealed class FormatHex : PSCmdlet | ||
| { | ||
|
rjmholt marked this conversation as resolved.
Outdated
vexx32 marked this conversation as resolved.
vexx32 marked this conversation as resolved.
vexx32 marked this conversation as resolved.
vexx32 marked this conversation as resolved.
vexx32 marked this conversation as resolved.
vexx32 marked this conversation as resolved.
vexx32 marked this conversation as resolved.
vexx32 marked this conversation as resolved.
vexx32 marked this conversation as resolved.
vexx32 marked this conversation as resolved.
|
||
| private const int BUFFERSIZE = 16; | ||
|
|
||
| /// <summary> | ||
| /// 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(). | ||
|
vexx32 marked this conversation as resolved.
Outdated
|
||
| /// </summary> | ||
| private readonly List<byte> _inputBuffer = new List<byte>(); | ||
|
|
||
| /// <summary> | ||
| /// Expect to group <see cref="InputObject"/>s by default. When receiving input that should not be grouped, | ||
| /// e.g., arrays, strings, FileInfo objects, this flag will be disabled until the next groupable | ||
| /// <see cref="InputObject"/> is received over the pipeline. | ||
| /// </summary> | ||
| private bool _groupInput = true; | ||
|
|
||
| /// <summary> | ||
| /// Keep track of prior input types to determine if we're given a heterogenous collection. | ||
| /// </summary> | ||
| private Type _lastInputType; | ||
|
|
||
| #region Parameters | ||
|
|
||
| /// <summary> | ||
|
|
@@ -85,19 +103,28 @@ public sealed class FormatHex : PSCmdlet | |
| /// </summary> | ||
| protected override void ProcessRecord() | ||
| { | ||
| if (string.Equals(this.ParameterSetName, "ByInputObject", StringComparison.OrdinalIgnoreCase)) | ||
| if (string.Equals(ParameterSetName, "ByInputObject", StringComparison.OrdinalIgnoreCase)) | ||
| { | ||
| ProcessObjectContent(InputObject); | ||
| ProcessInputObjects(InputObject); | ||
| } | ||
| else | ||
| { | ||
| List<string> pathsToProcess = string.Equals(this.ParameterSetName, "LiteralPath", StringComparison.OrdinalIgnoreCase) ? | ||
| ResolvePaths(LiteralPath, true) : ResolvePaths(Path, false); | ||
| List<string> pathsToProcess = string.Equals(ParameterSetName, "LiteralPath", StringComparison.OrdinalIgnoreCase) | ||
| ? ResolvePaths(LiteralPath, true) | ||
| : ResolvePaths(Path, false); | ||
|
|
||
| ProcessPath(pathsToProcess); | ||
| } | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Implements the EndProcessing method for the FormatHex command. | ||
| /// </summary> | ||
| protected override void EndProcessing() | ||
| { | ||
| FlushInputBuffer(); | ||
| } | ||
|
|
||
| #endregion | ||
|
|
||
| #region Paths | ||
|
|
@@ -114,15 +141,14 @@ private List<string> ResolvePaths(string[] path, bool literalPath) | |
| { | ||
| List<string> pathsToProcess = new List<string>(); | ||
| ProviderInfo provider = null; | ||
| PSDriveInfo drive = null; | ||
|
|
||
| foreach (string currentPath in path) | ||
| { | ||
| List<string> newPaths = new List<string>(); | ||
|
|
||
| if (literalPath) | ||
| { | ||
| newPaths.Add(Context.SessionState.Path.GetUnresolvedProviderPathFromPSPath(currentPath, out provider, out drive)); | ||
| newPaths.Add(Context.SessionState.Path.GetUnresolvedProviderPathFromPSPath(currentPath, out provider, out _)); | ||
| } | ||
| else | ||
| { | ||
|
|
@@ -183,84 +209,148 @@ private void ProcessFileContent(string path) | |
|
|
||
| try | ||
| { | ||
| using (BinaryReader reader = new BinaryReader(File.Open(path, FileMode.Open, FileAccess.Read, FileShare.Read))) | ||
| { | ||
| long offset = Offset; | ||
| int bytesRead = 0; | ||
| long count = 0; | ||
| using var reader = new BinaryReader(File.Open(path, FileMode.Open, FileAccess.Read, FileShare.Read)); | ||
| long offset = Offset; | ||
| int bytesRead = 0; | ||
| long count = 0; | ||
|
|
||
| reader.BaseStream.Position = Offset; | ||
| reader.BaseStream.Position = Offset; | ||
|
|
||
| while ((bytesRead = reader.Read(buffer)) > 0) | ||
| while ((bytesRead = reader.Read(buffer)) > 0) | ||
| { | ||
| count += bytesRead; | ||
| if (count > Count) | ||
| { | ||
| count += bytesRead; | ||
| if (count > Count) | ||
| { | ||
| bytesRead -= (int)(count - Count); | ||
| WriteHexadecimal(buffer.Slice(0, bytesRead), path, offset); | ||
| break; | ||
| } | ||
|
|
||
| bytesRead -= (int)(count - Count); | ||
| WriteHexadecimal(buffer.Slice(0, bytesRead), path, offset); | ||
|
|
||
| offset += bytesRead; | ||
| break; | ||
| } | ||
|
|
||
| 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) | ||
| { | ||
| WriteError(new ErrorRecord(argException, "FormatHexArgumentError", ErrorCategory.WriteError, path)); | ||
| } | ||
| catch (NotSupportedException notSupportedException) | ||
| { | ||
| WriteError(new ErrorRecord(notSupportedException, "FormatHexPathRefersToANonFileDevice", ErrorCategory.InvalidArgument, path)); | ||
| WriteError(new ErrorRecord( | ||
| notSupportedException, | ||
| "FormatHexPathRefersToANonFileDevice", | ||
| ErrorCategory.InvalidArgument, | ||
| path)); | ||
| } | ||
| catch (SecurityException securityException) | ||
| { | ||
| WriteError(new ErrorRecord(securityException, "FormatHexUnauthorizedAccessError", ErrorCategory.PermissionDenied, path)); | ||
| WriteError(new ErrorRecord( | ||
| securityException, | ||
| "FormatHexUnauthorizedAccessError", | ||
| ErrorCategory.PermissionDenied, | ||
| path)); | ||
| } | ||
| } | ||
|
|
||
| #endregion | ||
|
|
||
| #region InputObjects | ||
|
|
||
| private void ProcessString(string originalString) | ||
| { | ||
| Span<byte> bytes = Encoding.GetBytes(originalString); | ||
|
|
||
| int offset = Math.Min(bytes.Length, Offset < int.MaxValue ? (int)Offset : int.MaxValue); | ||
| int count = Math.Min(bytes.Length - offset, Count < int.MaxValue ? (int)Count : int.MaxValue); | ||
|
|
||
| if (offset != 0 || count != bytes.Length) | ||
| { | ||
| WriteHexadecimal(bytes.Slice(offset, count), offset: 0, label: GetGroupLabel(typeof(string))); | ||
| } | ||
| else | ||
| { | ||
| WriteHexadecimal(bytes, offset: 0, label: GetGroupLabel(typeof(string))); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should either consistently name the label param or don't, but it's a bit inconsistent.
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think I'll opt for naming it, since one of the other possible overloads also has a string param ( |
||
| } | ||
| } | ||
|
|
||
| private static readonly Random _idGenerator = new Random(); | ||
| private static string GetGroupLabel(Type inputType) | ||
| => string.Format("{0} ({1}) <{2:X8}>", inputType.Name, inputType.FullName, _idGenerator.Next()); | ||
|
|
||
| private void FlushInputBuffer() | ||
| { | ||
| if (_inputBuffer.Count == 0) | ||
| { | ||
| return; | ||
| } | ||
|
|
||
| int offset = Math.Min(_inputBuffer.Count, Offset < int.MaxValue ? (int)Offset : int.MaxValue); | ||
| int count = Math.Min(_inputBuffer.Count - offset, Count < int.MaxValue ? (int)Count : int.MaxValue); | ||
|
|
||
| if (offset != 0 || count != _inputBuffer.Count) | ||
| { | ||
| WriteHexadecimal( | ||
| _inputBuffer.GetRange(offset, count).ToArray(), | ||
| offset: 0, | ||
| label: GetGroupLabel(_lastInputType)); | ||
| } | ||
| else | ||
| { | ||
| WriteHexadecimal( | ||
| _inputBuffer.ToArray(), | ||
| offset: 0, | ||
| label: GetGroupLabel(_lastInputType)); | ||
| } | ||
|
|
||
| // Reset flags so we can go back to filling up the buffer when needed. | ||
| _lastInputType = null; | ||
| _groupInput = true; | ||
| _inputBuffer.Clear(); | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Creates a byte array from the object passed to the cmdlet (based on type) and passes | ||
| /// that array on to the WriteHexadecimal method to output. | ||
| /// </summary> | ||
| /// <param name="inputObject">The pipeline input object being processed.</param> | ||
| private void ProcessObjectContent(PSObject inputObject) | ||
| private void ProcessInputObjects(PSObject inputObject) | ||
| { | ||
| object obj = inputObject.BaseObject; | ||
|
|
||
| if (obj is System.IO.FileSystemInfo fsi) | ||
| if (obj is FileSystemInfo fsi) | ||
| { | ||
| // Output already processed objects first, then process the file input. | ||
| FlushInputBuffer(); | ||
| string[] path = { fsi.FullName }; | ||
| List<string> pathsToProcess = ResolvePaths(path, true); | ||
| ProcessPath(pathsToProcess); | ||
| return; | ||
| } | ||
|
|
||
| byte[] inputBytes = ConvertToByteArray(obj); | ||
| if (obj is string str) | ||
| { | ||
| // Output already processed objects first, then process the string input. | ||
| FlushInputBuffer(); | ||
| ProcessString(str); | ||
| return; | ||
| } | ||
|
|
||
| byte[] inputBytes = ConvertToBytes(obj); | ||
|
|
||
| if (!_groupInput) | ||
| { | ||
| FlushInputBuffer(); | ||
| } | ||
|
|
||
| if (inputBytes != null) | ||
| { | ||
| 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) | ||
| { | ||
| WriteHexadecimal(inputBytes.AsSpan().Slice(offset, count), null, 0); | ||
| } | ||
| else | ||
| { | ||
| WriteHexadecimal(inputBytes, null, 0); | ||
| } | ||
| _inputBuffer.AddRange(inputBytes); | ||
| } | ||
| else | ||
| { | ||
|
|
@@ -280,21 +370,20 @@ private void ProcessObjectContent(PSObject inputObject) | |
| /// </summary> | ||
| /// <param name="inputObject">The object to convert.</param> | ||
| /// <returns>Returns a byte array of the input values, or null if there is no available conversion path.</returns> | ||
| private byte[] ConvertToByteArray(object inputObject) | ||
| private byte[] ConvertToBytes(object inputObject) | ||
| { | ||
| if (inputObject is string str) | ||
| { | ||
| return Encoding.GetBytes(str); | ||
| } | ||
|
|
||
| var baseType = inputObject.GetType(); | ||
| Type baseType = inputObject.GetType(); | ||
| byte[] result = null; | ||
| int elements = 1; | ||
| bool isArray = false; | ||
| bool isBool = false; | ||
| bool isEnum = false; | ||
| if (baseType.IsArray) | ||
| { | ||
| FlushInputBuffer(); | ||
| _lastInputType = baseType; | ||
| _groupInput = false; | ||
|
|
||
| baseType = baseType.GetElementType(); | ||
| dynamic dynamicObject = inputObject; | ||
| elements = (int)dynamicObject.Length; | ||
|
|
@@ -309,6 +398,16 @@ private byte[] ConvertToByteArray(object inputObject) | |
|
|
||
| if (baseType.IsPrimitive && elements > 0) | ||
| { | ||
| if (_groupInput) | ||
| { | ||
| if (_lastInputType != null && baseType != _lastInputType) | ||
| { | ||
| _groupInput = false; | ||
| } | ||
|
|
||
| _lastInputType = baseType; | ||
| } | ||
|
|
||
| if (baseType == typeof(bool)) | ||
| { | ||
| isBool = true; | ||
|
|
@@ -364,21 +463,44 @@ private byte[] ConvertToByteArray(object inputObject) | |
| #region Output | ||
|
|
||
| /// <summary> | ||
| /// Outputs the hexadecimial representation of the input data. | ||
| /// Outputs the hexadecimal representation of the input data. | ||
| /// </summary> | ||
| /// <param name="inputBytes">Bytes for the hexadecimial representation.</param> | ||
| /// <param name="inputBytes">Bytes for the hexadecimal representation.</param> | ||
| /// <param name="path">File path.</param> | ||
| /// <param name="offset">Offset in the file.</param> | ||
| private void WriteHexadecimal(Span<byte> inputBytes, string path, long offset) | ||
| { | ||
| ByteCollection byteCollectionObject = new ByteCollection((ulong)offset, inputBytes.ToArray(), path); | ||
| WriteObject(byteCollectionObject); | ||
| var bytesPerObject = 16; | ||
| for (int index = 0; index < inputBytes.Length; index += bytesPerObject) | ||
| { | ||
| var count = inputBytes.Length - index < bytesPerObject | ||
| ? inputBytes.Length - index | ||
| : bytesPerObject; | ||
| var bytes = inputBytes.Slice(index, count); | ||
| WriteObject(new ByteCollection((ulong)index + (ulong)offset, bytes.ToArray(), path)); | ||
| } | ||
| } | ||
|
|
||
| private void WriteHexadecimal(byte[] inputBytes, string path, long offset) | ||
| /// <summary> | ||
| /// Outputs the hexadecimal representation of the input data. | ||
| /// </summary> | ||
| /// <param name="inputBytes">Bytes for the hexadecimal representation.</param> | ||
| /// <param name="offset">Offset in the file.</param> | ||
| /// <param name="label"> | ||
| /// The label for the byte group. This may be a file path, a string value, or a | ||
| /// formatted identifying string for the group. | ||
| /// </param> | ||
| private void WriteHexadecimal(Span<byte> inputBytes, long offset, string label) | ||
| { | ||
| ByteCollection byteCollectionObject = new ByteCollection((ulong)offset, inputBytes, path); | ||
| WriteObject(byteCollectionObject); | ||
| var bytesPerObject = 16; | ||
| for (int index = 0; index < inputBytes.Length; index += bytesPerObject) | ||
| { | ||
| var count = inputBytes.Length - index < bytesPerObject | ||
| ? inputBytes.Length - index | ||
| : bytesPerObject; | ||
| var bytes = inputBytes.Slice(index, count); | ||
| WriteObject(new ByteCollection((ulong)index + (ulong)offset, label, bytes.ToArray())); | ||
| } | ||
| } | ||
|
|
||
| #endregion | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.