Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
@@ -0,0 +1,329 @@
using System;
using System.IO;
using System.Text;
using System.Security;
using System.Management.Automation;
using System.Collections.Generic;
using System.Management.Automation.Internal;
// Once Serialization is available on CoreCLR: using System.Runtime.Serialization.Formatters.Binary;

namespace Microsoft.PowerShell.Commands
{
/// <summary>
/// Displays the hexidecimal equivalent of the input data.
/// <summary>
[Cmdlet(VerbsCommon.Format, "Hex", SupportsShouldProcess = true, HelpUri ="https://go.microsoft.com/fwlink/?LinkId=526919")]
[Alias ("fhx")]
[OutputType(typeof(Microsoft.PowerShell.Commands.ByteCollection))]
public sealed class FormatHex : PSCmdlet
{
private const int BUFFERSIZE = 16;

#region Parameters
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there any chance we could get Count and Offset parameters? I could probably live without Offset but on the PSCX Format-Hex command, I use -Count almost all the time. That's because I use it to see if a text file has a BOM, in which case I only want to see the first 3 characters.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Those are good suggestions, but out of scope for this PR.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@rkeithhill, please open a issue for that suggestion. Thanks.


/// <summary>
/// Path of file(s) to process
/// </summary>
[Parameter(Mandatory = true, Position = 0, ParameterSetName = "Path")]
[ValidateNotNullOrEmpty()]
public string[] Path { get; set; }

/// <summary>
/// Literal path of file to process
/// </summary>
[Parameter(Mandatory = true, ParameterSetName = "LiteralPath")]
[ValidateNotNullOrEmpty()]
[Alias("PSPath")]
public string[] LiteralPath { get; set; }

/// <summary>
/// Ojbect to process
/// </summary>
[Parameter(Mandatory = true, ParameterSetName = "ByInputObject", ValueFromPipeline = true)]
public PSObject InputObject { get; set; }

/// <summary>
/// Type of character encoding for InputObject
/// </summary>
[Parameter(ParameterSetName = "ByInputObject")]
[ValidateSetAttribute(new string[] {
EncodingConversion.Unicode,
EncodingConversion.BigEndianUnicode,
EncodingConversion.Utf8,
EncodingConversion.Utf7,
EncodingConversion.Utf32,
EncodingConversion.Ascii})]
public string Encoding { get; set; } = "Ascii";

/// <summary>
/// This parameter is no-op
/// </summary>
[Parameter(ParameterSetName = "ByInputObject")]
public SwitchParameter Raw { get; set; }

#endregion

#region Overrides

/// <summary>
/// Implements the ProcessRecord method for the FormatHex command.
/// </summary>
protected override void ProcessRecord()
{
if (String.Equals(this.ParameterSetName, "ByInputObject", StringComparison.OrdinalIgnoreCase))
{
ProcessObjectContent(InputObject);
}
else
{
List<string> pathsToProcess = String.Equals(this.ParameterSetName, "LiteralPath", StringComparison.OrdinalIgnoreCase) ?
ResolvePaths(LiteralPath, true) : ResolvePaths(Path, false);

ProcessPath(pathsToProcess);
}
}

#endregion

#region Paths

/// <summary>
/// Validate each path provided and if valid, add to array of paths to process.
/// 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.
/// </summary>
/// <param name="path"></param>
/// <param name="literalPath"></param>
/// <returns></returns>
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));
}
else
{
try
{
newPaths.AddRange(Context.SessionState.Path.GetResolvedProviderPathFromPSPath(currentPath, out provider));
}
catch (ItemNotFoundException e)
{
if (!WildcardPattern.ContainsWildcardCharacters(currentPath))
{
ErrorRecord errorRecord = new ErrorRecord(e, "FileNotFound", ErrorCategory.ObjectNotFound, path);
WriteError(errorRecord);
continue;
}
}
}

if (!provider.Name.Equals("FileSystem", StringComparison.OrdinalIgnoreCase))
{
// 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);
WriteError(errorRecord);
continue;
}

pathsToProcess.AddRange(newPaths);
}

return pathsToProcess;
}

/// <summary>
/// Pass each valid path on to process its contents.
/// </summary>
/// <param name="pathsToProcess"></param>
private void ProcessPath(List<string> pathsToProcess)
{
foreach (string path in pathsToProcess)
{
ProcessFileContent(path);
}
}

/// <summary>
/// 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 ConvertToHexidecimal method to output.
/// </summary>
/// <param name="path"></param>
private void ProcessFileContent(string path)
{
byte[] buffer = new byte[BUFFERSIZE];

try
{
using (BinaryReader reader = new BinaryReader(File.Open(path, FileMode.Open, FileAccess.Read)))
{
UInt32 offset = 0;
Int32 bytesRead = 0;

while ((bytesRead = reader.Read(buffer, 0, BUFFERSIZE)) > 0)
{
if (bytesRead == BUFFERSIZE)
{
// We are reusing the same buffer so if we save the output to a variable, the variable
// will just contain multiple references to the same buffer memory space (containing only the
// last bytes of the file read). Copying the buffer allows us to pass the values on without
// overwriting previous values.
byte[] copyOfBuffer = new byte[16];
Array.Copy(buffer, 0, copyOfBuffer, 0, bytesRead);
ConvertToHexidecimal(copyOfBuffer, path, offset);
}
else
{
// Handle the case of a partial (and probably last) buffer. Copies the bytes read into a new,
// shorter array so we do not have the extra bytes from the previous pass through at the end.
byte[] remainingBytes = new byte[bytesRead];
Array.Copy(buffer, 0, remainingBytes, 0, bytesRead);
ConvertToHexidecimal(remainingBytes, path, offset);
}
// Update offset value.
offset += (UInt32)bytesRead;
}
}
}
catch (IOException ioException)
{
// IOException takes care of FileNotFoundException, DirectoryNotFoundException, and PathTooLongException
WriteError(new ErrorRecord(ioException, "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));
}
catch (SecurityException securityException)
{
WriteError(new ErrorRecord(securityException, "FormatHexUnauthorizedAccessError", ErrorCategory.PermissionDenied, path));
}
}

#endregion

#region InputObjects

/// <summary>
/// Creates a byte array from the object passed to the cmdlet (based on type) and passes
/// that array on to the ConvertToHexidecimal method to output.
/// </summary>
/// <param name="inputObject"></param>
private void ProcessObjectContent(PSObject inputObject)
{
Object obj = inputObject.BaseObject;
byte[] inputBytes = null;
if (obj is System.IO.FileSystemInfo)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know this is just a translation of the C#, but I don't think this case makes sense.

If I pass an object for Format-Hex, I expect that object to be formatted. I didn't ask for the contents of the file, that's a different parameter set.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Per committee review/comment below, we'll leave as is for now.

{
string[] path = { ((FileSystemInfo)obj).FullName };
List<string> pathsToProcess = ResolvePaths(path, true);
ProcessPath(pathsToProcess);
}

else if (obj is string)
{
string inputString = obj.ToString();
Encoding resolvedEncoding = EncodingConversion.Convert(this, Encoding);
inputBytes = resolvedEncoding.GetBytes(inputString);
}

else if (obj is byte)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be nice to see all primitive types (including enums) and arrays of these types - instead of this random assortment.

I wrote some code that covers much of what I think is useful and it's a similar number of lines of code, see https://gist.github.com/lzybkr/9940b63c8301c31316bdb3ec6305536f

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It might also be useful to support any value type, not just primitives, but that is a little harder.

Copy link
Contributor

@Francisco-Gamino Francisco-Gamino Mar 17, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @lzybkr, @MiaRomero Leap Program assignment/internship ends today, so I will follow-up on this. I have open an issue #3358 to track this. Thanks.

{
inputBytes = new byte[] { (byte)obj };
}

else if (obj is byte[])
{
inputBytes = ((byte[])obj);
}

else if (obj is Int32)
{
inputBytes = BitConverter.GetBytes((Int32)obj);
}

else if (obj is Int32[])
{
List<byte> inputStreamArray = new List<byte>();
Int32[] inputInts = (Int32[])obj;
foreach (Int32 value in inputInts)
{
byte[] tempBytes = BitConverter.GetBytes(value);
inputStreamArray.AddRange(tempBytes);
}
inputBytes = inputStreamArray.ToArray();
}

else if (obj is Int64)
{
inputBytes = BitConverter.GetBytes((Int64)obj);
}

else if (obj is Int64[])
{
List<byte> inputStreamArray = new List<byte>();
Int64[] inputInts = (Int64[])obj;
foreach (Int64 value in inputInts)
{
byte[] tempBytes = BitConverter.GetBytes(value);
inputStreamArray.AddRange(tempBytes);
}
inputBytes = inputStreamArray.ToArray();
}

// If the object type is not supported, throw an error. Once Serialization is
// available on CoreCLR, other types will be supported.
else
{
string errorMessage = StringUtil.Format(UtilityCommonStrings.FormatHexTypeNotSupported, obj.GetType());
ErrorRecord errorRecord = new ErrorRecord(new ArgumentException(errorMessage),

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would fix indentation here

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed for all error messages.

"FormatHexTypeNotSupported",
ErrorCategory.InvalidArgument,
obj.GetType());
WriteError(errorRecord);
}

if (inputBytes != null)
{
ConvertToHexidecimal(inputBytes, null, 0);
}
}

#endregion

#region Output

/// <summary>
/// Outputs the hexadecimial representaion of the of the input data.
/// </summary>
/// <param name="inputBytes"></param>
/// <param name="path"></param>
/// <param name="offset"></param>
private void ConvertToHexidecimal(byte[] inputBytes, string path, UInt32 offset)
{
if (inputBytes != null)
{
ByteCollection byteCollectionObject = new ByteCollection(offset, inputBytes, path);
WriteObject(byteCollectionObject);
}
}

#endregion
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -124,16 +124,6 @@ public static class UtilityResources
/// </summary>
public static string FileReadError { get { return UtilityCommonStrings.FileReadError; } }

/// <summary>
/// Error message to indicate that Format-Hex cmdlet does not directly support the type provided as input.
/// </summary>
public static string FormatHexTypeNotSupported { get { return UtilityCommonStrings.FormatHexTypeNotSupported; } }

/// <summary>
/// Error message to indicate that Format-Hex cmdlet does not directly support the type provided as input.
/// </summary>
public static string FormatHexResolvePathError { get { return UtilityCommonStrings.FormatHexResolvePathError; } }

/// <summary>
/// The resource string used to indicate 'PATH:' in the formating header.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -145,10 +145,10 @@
<value>The file '{0}' cannot be read: {1}</value>
</data>
<data name="FormatHexTypeNotSupported" xml:space="preserve">
<value>Cannot convert input of type {0} to hexadecimal. To view the hexadecimal formatting of its string representation, pipe it to the Out-String cmdlet before piping it to Format-Hex.</value>
<value>Cannot convert input of type '{0}' to hexadecimal. To view the hexadecimal formatting of its string representation, pipe it to the Out-String cmdlet before piping it to Format-Hex.</value>
</data>
<data name="FormatHexResolvePathError" xml:space="preserve">
<value>Cannot display the context of {0} as hex. The path resolves to multiple files.</value>
<data name="FormatHexOnlySupportsFileSystemPaths" xml:space="preserve">
<value>The given path '{0}' is not supported. This command only supports the FileSystem Provider paths.</value>
</data>
<data name="FormatHexPathPrefix" xml:space="preserve">
<value>Path: </value>
Expand Down
Loading