diff --git a/.gitignore b/.gitignore
index e97d30b..199848a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -8,3 +8,5 @@ project.lock.json
Microsoft.PowerShell.TextUtility.xml
# VSCode directories that are not at the repository root
/**/.vscode/
+# Visual Studio IDE directory
+.vs/
\ No newline at end of file
diff --git a/README.md b/README.md
index 3ddb4aa..62734e7 100644
--- a/README.md
+++ b/README.md
@@ -20,6 +20,10 @@ Return a base64 encoded representation of a string.
This will convert tabular data and convert it to an object.
+## Get-FileEncoding
+
+This cmdlet returns encoding for a file.
+
## Code of Conduct
Please see our [Code of Conduct](.github/CODE_OF_CONDUCT.md) before participating in this project.
diff --git a/TextUtility.sln b/TextUtility.sln
new file mode 100644
index 0000000..29764b9
--- /dev/null
+++ b/TextUtility.sln
@@ -0,0 +1,30 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 17
+VisualStudioVersion = 17.5.002.0
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{CBF81F7C-6E0A-4695-AA0F-35C61676B7EB}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.PowerShell.TextUtility", "src\code\Microsoft.PowerShell.TextUtility.csproj", "{B5249E6A-DBD6-49AD-B78B-DFF09CB9D26F}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {B5249E6A-DBD6-49AD-B78B-DFF09CB9D26F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {B5249E6A-DBD6-49AD-B78B-DFF09CB9D26F}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {B5249E6A-DBD6-49AD-B78B-DFF09CB9D26F}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {B5249E6A-DBD6-49AD-B78B-DFF09CB9D26F}.Release|Any CPU.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(NestedProjects) = preSolution
+ {B5249E6A-DBD6-49AD-B78B-DFF09CB9D26F} = {CBF81F7C-6E0A-4695-AA0F-35C61676B7EB}
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {EAC68D5E-4DA0-4F37-88B6-CAF32652C01F}
+ EndGlobalSection
+EndGlobal
diff --git a/src/Microsoft.PowerShell.TextUtility.psd1 b/src/Microsoft.PowerShell.TextUtility.psd1
index ddb5209..7e2372c 100644
--- a/src/Microsoft.PowerShell.TextUtility.psd1
+++ b/src/Microsoft.PowerShell.TextUtility.psd1
@@ -13,7 +13,7 @@
PowerShellVersion = '5.1'
FormatsToProcess = @('Microsoft.PowerShell.TextUtility.format.ps1xml')
CmdletsToExport = @(
- 'Compare-Text','ConvertFrom-Base64','ConvertTo-Base64',"ConvertFrom-TextTable"
+ 'Compare-Text','ConvertFrom-Base64','ConvertTo-Base64',"ConvertFrom-TextTable","Get-FileEncoding"
)
PrivateData = @{
PSData = @{
diff --git a/src/code/GetFileEncodingCommand.cs b/src/code/GetFileEncodingCommand.cs
new file mode 100644
index 0000000..5fee905
--- /dev/null
+++ b/src/code/GetFileEncodingCommand.cs
@@ -0,0 +1,70 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using System.IO;
+using System.Management.Automation;
+using System.Text;
+
+namespace Microsoft.PowerShell.TextUtility
+{
+ ///
+ /// This class implements the Get-FileEncoding command.
+ ///
+ [Cmdlet(VerbsCommon.Get, "FileEncoding", DefaultParameterSetName = PathParameterSet)]
+ [OutputType(typeof(Encoding))]
+ public sealed class GetFileEncodingCommand : PSCmdlet
+ {
+ #region Parameter Sets
+
+ private const string PathParameterSet = "ByPath";
+ private const string LiteralPathParameterSet = "ByLiteralPath";
+
+ #endregion
+
+ #region Parameters
+
+ ///
+ /// Gets or sets path from from which to get encoding.
+ ///
+ [Parameter(Position = 0, Mandatory = true, ValueFromPipeline = true, ValueFromPipelineByPropertyName = true, ParameterSetName = PathParameterSet)]
+ public string Path { get; set; }
+
+ ///
+ /// Gets or sets literal path from which to get encoding.
+ ///
+ [Parameter(Position = 0, Mandatory = true, ValueFromPipeline = true, ValueFromPipelineByPropertyName = true, ParameterSetName = LiteralPathParameterSet)]
+ [Alias("PSPath", "LP")]
+ public string LiteralPath
+ {
+ get
+ {
+ return _isLiteralPath ? Path : null;
+ }
+
+ set
+ {
+ Path = value;
+ _isLiteralPath = true;
+ }
+ }
+
+ private bool _isLiteralPath;
+
+ #endregion
+
+ ///
+ /// Process paths to get file encoding.
+ ///
+ protected override void ProcessRecord()
+ {
+ string resolvedPath = PathUtils.ResolveFilePath(Path, this, _isLiteralPath);
+
+ if (!File.Exists(resolvedPath))
+ {
+ PathUtils.ReportPathNotFound(Path, this);
+ }
+
+ WriteObject(PathUtils.GetPathEncoding(resolvedPath));
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/code/Microsoft.PowerShell.TextUtility.csproj b/src/code/Microsoft.PowerShell.TextUtility.csproj
index ad59249..9102fe6 100644
--- a/src/code/Microsoft.PowerShell.TextUtility.csproj
+++ b/src/code/Microsoft.PowerShell.TextUtility.csproj
@@ -17,7 +17,7 @@
all
-
+
all
@@ -28,4 +28,19 @@
+
+
+ True
+ True
+ PathUtilityStrings.resx
+
+
+
+
+
+ ResXFileCodeGenerator
+ PathUtilityStrings.Designer.cs
+
+
+
diff --git a/src/code/PathUtils.cs b/src/code/PathUtils.cs
new file mode 100644
index 0000000..3e91b40
--- /dev/null
+++ b/src/code/PathUtils.cs
@@ -0,0 +1,115 @@
+using System;
+using System.Collections.ObjectModel;
+using System.Globalization;
+using System.IO;
+using System.Management.Automation;
+using System.Text;
+using Microsoft.PowerShell.TextUtility.Resources;
+
+namespace Microsoft.PowerShell.TextUtility
+{
+ ///
+ /// Defines generic path utilities and helper methods for TextUtility.
+ ///
+ internal static class PathUtils
+ {
+ ///
+ /// Resolves user provided path using file system provider.
+ ///
+ /// The path to resolve.
+ /// The command.
+ /// True if the wildcard resolution should not be attempted.
+ /// The resolved (absolute) path.
+ internal static string ResolveFilePath(string path, PSCmdlet command, bool isLiteralPath)
+ {
+ string resolvedPath;
+
+ try
+ {
+ ProviderInfo provider = null;
+ PSDriveInfo drive = null;
+
+ PathIntrinsics sessionStatePath = command.SessionState.Path;
+
+ if (isLiteralPath)
+ {
+ resolvedPath = sessionStatePath.GetUnresolvedProviderPathFromPSPath(path, out provider, out drive);
+ }
+ else
+ {
+ Collection filePaths = sessionStatePath.GetResolvedProviderPathFromPSPath(path, out provider);
+
+ if (!provider.Name.Equals("FileSystem", StringComparison.OrdinalIgnoreCase))
+ {
+ ReportOnlySupportsFileSystemPaths(path, command);
+ }
+
+ if (filePaths.Count > 1)
+ {
+ ReportMultipleFilesNotSupported(command);
+ }
+
+ resolvedPath = filePaths[0];
+ }
+ }
+ catch (ItemNotFoundException)
+ {
+ resolvedPath = null;
+ }
+
+ return resolvedPath;
+ }
+
+ ///
+ /// Throws terminating error for not using file system provider.
+ ///
+ /// The path to report.
+ /// The command.
+ internal static void ReportOnlySupportsFileSystemPaths(string path, PSCmdlet command)
+ {
+ var errorMessage = string.Format(CultureInfo.CurrentCulture, PathUtilityStrings.OnlySupportsFileSystemPaths, path);
+ var exception = new ArgumentException(errorMessage);
+ var errorRecord = new ErrorRecord(exception, "OnlySupportsFileSystemPaths", ErrorCategory.InvalidArgument, path);
+ command.ThrowTerminatingError(errorRecord);
+ }
+
+ ///
+ /// Throws terminating error for path not found.
+ ///
+ /// The path to report.
+ /// The command.
+ internal static void ReportPathNotFound(string path, PSCmdlet command)
+ {
+ var errorMessage = string.Format(CultureInfo.CurrentCulture, PathUtilityStrings.PathNotFound, path);
+ var exception = new ArgumentException(errorMessage);
+ var errorRecord = new ErrorRecord(exception, "PathNotFound", ErrorCategory.ObjectNotFound, path);
+ command.ThrowTerminatingError(errorRecord);
+ }
+
+ ///
+ /// Throws terminating error for multiple files being used.
+ ///
+ /// The command.
+ internal static void ReportMultipleFilesNotSupported(PSCmdlet command)
+ {
+ var errorMessage = string.Format(CultureInfo.CurrentCulture, PathUtilityStrings.MultipleFilesNotSupported);
+ var exception = new ArgumentException(errorMessage);
+ var errorRecord = new ErrorRecord(exception, "MultipleFilesNotSupported", ErrorCategory.InvalidArgument, null);
+ command.ThrowTerminatingError(errorRecord);
+ }
+
+ ///
+ /// Gets encoding for path.
+ ///
+ /// The path to get file encoding.
+ /// The encoding of file.
+ internal static Encoding GetPathEncoding(string path)
+ {
+ using (var reader = new StreamReader(path, Encoding.Default, detectEncodingFromByteOrderMarks: true))
+ {
+ _ = reader.Read();
+ return reader.CurrentEncoding;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/code/Resources/PathUtilityStrings.Designer.cs b/src/code/Resources/PathUtilityStrings.Designer.cs
new file mode 100644
index 0000000..5974d58
--- /dev/null
+++ b/src/code/Resources/PathUtilityStrings.Designer.cs
@@ -0,0 +1,105 @@
+//------------------------------------------------------------------------------
+//
+// This code was generated by a tool.
+// Runtime Version:4.0.30319.42000
+//
+// Changes to this file may cause incorrect behavior and will be lost if
+// the code is regenerated.
+//
+//------------------------------------------------------------------------------
+
+namespace Microsoft.PowerShell.TextUtility.Resources
+{
+ using System;
+
+
+ ///
+ /// A strongly-typed resource class, for looking up localized strings, etc.
+ ///
+ // This class was auto-generated by the StronglyTypedResourceBuilder
+ // class via a tool like ResGen or Visual Studio.
+ // To add or remove a member, edit your .ResX file then rerun ResGen
+ // with the /str option, or rebuild your VS project.
+ [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")]
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+ [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
+ internal class PathUtilityStrings
+ {
+
+ private static global::System.Resources.ResourceManager resourceMan;
+
+ private static global::System.Globalization.CultureInfo resourceCulture;
+
+ [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
+ internal PathUtilityStrings()
+ {
+ }
+
+ ///
+ /// Returns the cached ResourceManager instance used by this class.
+ ///
+ [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
+ internal static global::System.Resources.ResourceManager ResourceManager
+ {
+ get
+ {
+ if (object.ReferenceEquals(resourceMan, null))
+ {
+ global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Microsoft.PowerShell.TextUtility.Resources.PathUtilityStrings", typeof(PathUtilityStrings).Assembly);
+ resourceMan = temp;
+ }
+ return resourceMan;
+ }
+ }
+
+ ///
+ /// Overrides the current thread's CurrentUICulture property for all
+ /// resource lookups using this strongly typed resource class.
+ ///
+ [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
+ internal static global::System.Globalization.CultureInfo Culture
+ {
+ get
+ {
+ return resourceCulture;
+ }
+ set
+ {
+ resourceCulture = value;
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Cannot perform operation because the path resolved to more than one file. This command cannot operate on multiple files..
+ ///
+ internal static string MultipleFilesNotSupported
+ {
+ get
+ {
+ return ResourceManager.GetString("MultipleFilesNotSupported", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to The given path '{0}' is not supported. This command only supports the FileSystem Provider paths..
+ ///
+ internal static string OnlySupportsFileSystemPaths
+ {
+ get
+ {
+ return ResourceManager.GetString("OnlySupportsFileSystemPaths", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Cannot find path '{0}' because it does not exist..
+ ///
+ internal static string PathNotFound
+ {
+ get
+ {
+ return ResourceManager.GetString("PathNotFound", resourceCulture);
+ }
+ }
+ }
+}
diff --git a/src/code/Resources/PathUtilityStrings.resx b/src/code/Resources/PathUtilityStrings.resx
new file mode 100644
index 0000000..23ee479
--- /dev/null
+++ b/src/code/Resources/PathUtilityStrings.resx
@@ -0,0 +1,129 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ text/microsoft-resx
+
+
+ 2.0
+
+
+ System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ Cannot perform operation because the path resolved to more than one file. This command cannot operate on multiple files.
+
+
+ The given path '{0}' is not supported. This command only supports the FileSystem Provider paths.
+
+
+ Cannot find path '{0}' because it does not exist.
+
+
\ No newline at end of file
diff --git a/test/Get-FileEncoding.Tests.ps1 b/test/Get-FileEncoding.Tests.ps1
new file mode 100644
index 0000000..10c2a21
--- /dev/null
+++ b/test/Get-FileEncoding.Tests.ps1
@@ -0,0 +1,52 @@
+# Copyright (c) Microsoft Corporation.
+# Licensed under the MIT License.
+
+Describe "Get-FileEncoding" -Tags "CI" {
+ BeforeAll {
+ $testFilePath = Join-Path -Path $TestDrive -ChildPath test.txt
+ $testFileLiteralPath = Join-Path -Path $TestDrive -ChildPath "[test].txt"
+ $content = 'abc'
+
+ $testCases = @(
+ @{ EncodingName = 'ascii'; ExpectedEncoding = 'utf-8' }
+ @{ EncodingName = 'bigendianunicode'; ExpectedEncoding = 'utf-16BE' }
+ @{ EncodingName = 'bigendianutf32'; ExpectedEncoding = 'utf-32BE' }
+ @{ EncodingName = 'oem'; ExpectedEncoding = 'utf-8' }
+ @{ EncodingName = 'unicode'; ExpectedEncoding = 'utf-16' }
+ @{ EncodingName = 'utf8'; ExpectedEncoding = 'utf-8' }
+ @{ EncodingName = 'utf8BOM'; ExpectedEncoding = 'utf-8' }
+ @{ EncodingName = 'utf8NoBOM'; ExpectedEncoding = 'utf-8' }
+ @{ EncodingName = 'utf32'; ExpectedEncoding = 'utf-32' }
+ )
+ }
+
+ It "Validate Get-FileEncoding using -Path returns file encoding for ''" -TestCases $testCases {
+ param($EncodingName, $ExpectedEncoding)
+ Set-Content -Path $testFilePath -Encoding $EncodingName -Value $content -Force
+ (Get-FileEncoding -Path $testFilePath).BodyName | Should -Be $ExpectedEncoding
+ (Get-ChildItem -Path $testFilePath | Get-FileEncoding).BodyName | Should -Be $ExpectedEncoding
+ }
+
+ It "Validate Get-FileEncoding using -LiteralPath returns file encoding for ''" -TestCases $testCases {
+ param($EncodingName, $ExpectedEncoding)
+ Set-Content -LiteralPath $testFileLiteralPath -Encoding $EncodingName -Value $content -Force
+ (Get-FileEncoding -LiteralPath $testFileLiteralPath).BodyName | Should -Be $ExpectedEncoding
+ (Get-ChildItem -LiteralPath $testFileLiteralPath | Get-FileEncoding).BodyName | Should -Be $ExpectedEncoding
+ }
+
+ It "Should throw exception if path is not found using -Path" {
+ { Get-FileEncoding -Path nonexistentpath } | Should -Throw -ErrorId 'PathNotFound,Microsoft.PowerShell.TextUtility.GetFileEncodingCommand'
+ }
+
+ It "Should throw exception if path is not found using -LiteralPath" {
+ { Get-FileEncoding -LiteralPath nonexistentpath } | Should -Throw -ErrorId 'PathNotFound,Microsoft.PowerShell.TextUtility.GetFileEncodingCommand'
+ }
+
+ It "Should throw exception if path is not file system path" {
+ { Get-FileEncoding -Path 'Env:' } | Should -Throw -ErrorId 'OnlySupportsFileSystemPaths,Microsoft.PowerShell.TextUtility.GetFileEncodingCommand'
+ }
+
+ It "Should throw exception if multiple paths is specified" {
+ { Get-FileEncoding -Path '*' } | Should -Throw -ErrorId 'MultipleFilesNotSupported,Microsoft.PowerShell.TextUtility.GetFileEncodingCommand'
+ }
+}
\ No newline at end of file