Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
186b3b2
:sparkles: Add better handling for taking mixed-type pipeline input
vexx32 Jan 17, 2019
caa70c6
:wrench: Use same error for both input modes
vexx32 Jan 17, 2019
6e74e26
:white_check_mark: Add tests for new behaviours
vexx32 Jan 17, 2019
09da3d1
:recycle: Tidy ErrorRecord instantiations to CodeFactor standards
vexx32 Jan 17, 2019
eeac89c
:white_check_mark: Address James' comment
vexx32 Jun 23, 2019
94d5c3d
:construction: WIP formatview for ByteCollection
vexx32 Jul 17, 2019
f2a90a8
:construction: add ByteCollection helper methods
vexx32 Jul 29, 2019
3265993
:art: Add format definition for ByteCollection
vexx32 Jul 29, 2019
045ebd6
:wrench: Address Ilya's review comments
vexx32 Jul 30, 2019
2212508
:art: Fix formatview issues
vexx32 Oct 3, 2019
de428f7
Merge remote-tracking branch 'upstream/master' into FormatHexRefactor
vexx32 Oct 7, 2019
3956925
:construction: Output per-line objects
vexx32 Oct 7, 2019
af5f72b
:construction: refactor output formats and grouping
vexx32 Oct 7, 2019
2ff9958
:sparkles: Add SourceType to ByteCollection
vexx32 Oct 8, 2019
b2ff7c7
:art: Add GroupBy to std format
vexx32 Oct 8, 2019
89656bd
:construction: :art: Finish grouping logic
vexx32 Oct 8, 2019
3c53956
:construction: Add backing properties
vexx32 Oct 8, 2019
77caa05
:art: group bytecollection tables naturally
vexx32 Oct 8, 2019
3d8955c
:construction: Overhaul ByteCollection
vexx32 Oct 9, 2019
116f096
:art: Update bytecollection formatter
vexx32 Oct 9, 2019
d3563a4
:recycle: Refactor Format-Hex to permit grouping
vexx32 Oct 9, 2019
06793e7
:bug: Fix issue with consecutive arrays over pipe
vexx32 Oct 9, 2019
643bdcf
:recycle: Tidy up FormatHexCommand
vexx32 Oct 9, 2019
65f9cbe
:art: Update the .ToString() for ByteCollection
vexx32 Oct 9, 2019
61722d2
:recycle: Cleanup ByteCollection
vexx32 Oct 9, 2019
f78bfa9
:white_check_mark: Finish updating tests
vexx32 Oct 9, 2019
e12caad
:art: Set fixed width for the random ID
vexx32 Oct 10, 2019
feb5d57
:art: Codacy/CodeFactor fixes
vexx32 Oct 10, 2019
a2ae168
:art: Use consistent param naming
vexx32 Oct 12, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,30 @@ namespace Microsoft.PowerShell.Commands
/// Displays the hexadecimal equivalent of the input data.
Comment thread
vexx32 marked this conversation as resolved.
Outdated
Comment thread
vexx32 marked this conversation as resolved.
Outdated
Comment thread
vexx32 marked this conversation as resolved.
Comment thread
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
{
Comment thread
rjmholt marked this conversation as resolved.
Outdated
Comment thread
vexx32 marked this conversation as resolved.
Comment thread
vexx32 marked this conversation as resolved.
Comment thread
vexx32 marked this conversation as resolved.
Comment thread
vexx32 marked this conversation as resolved.
Comment thread
vexx32 marked this conversation as resolved.
Comment thread
vexx32 marked this conversation as resolved.
Comment thread
vexx32 marked this conversation as resolved.
Comment thread
vexx32 marked this conversation as resolved.
Comment thread
vexx32 marked this conversation as resolved.
Comment thread
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&lt;byte&gt; and then output the formatted result all at once in EndProcessing().
Comment thread
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>
Expand Down Expand Up @@ -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
Expand All @@ -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
{
Expand Down Expand Up @@ -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)));
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The 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.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The 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 (path), so it's safer to name this one.

}
}

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
{
Expand All @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -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
Expand Down
Loading