From fb7e64eb1d5287eec75021f6b555fa98cb267d33 Mon Sep 17 00:00:00 2001 From: ArmaanMcleod Date: Sat, 4 Nov 2023 22:35:49 +1100 Subject: [PATCH 1/9] Added initial Get-FileEncoding cmdlet and files --- .gitignore | 2 + TextUtility.sln | 30 ++++ src/Microsoft.PowerShell.TextUtility.psd1 | 2 +- src/code/GetFileEncodingCommand.cs | 167 ++++++++++++++++++ .../Microsoft.PowerShell.TextUtility.csproj | 17 +- src/code/Properties/Resources.Designer.cs | 90 ++++++++++ src/code/Properties/Resources.resx | 129 ++++++++++++++ test/Get-FileEncoding.Tests.ps1 | 52 ++++++ 8 files changed, 487 insertions(+), 2 deletions(-) create mode 100644 TextUtility.sln create mode 100644 src/code/GetFileEncodingCommand.cs create mode 100644 src/code/Properties/Resources.Designer.cs create mode 100644 src/code/Properties/Resources.resx create mode 100644 test/Get-FileEncoding.Tests.ps1 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/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..b112982 --- /dev/null +++ b/src/code/GetFileEncodingCommand.cs @@ -0,0 +1,167 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System; +using System.Collections.ObjectModel; +using System.Globalization; +using System.IO; +using System.Management.Automation; +using System.Text; +using Microsoft.PowerShell.TextUtility.Properties; + +namespace Microsoft.PowerShell.Commands +{ + /// + /// 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 = ResolveFilePath(Path, _isLiteralPath); + + if (!File.Exists(resolvedPath)) + { + ReportPathNotFound(Path); + } + + WriteObject(GetPathEncoding(resolvedPath)); + } + + /// + /// Resolves user provided path using file system provider. + /// + /// The path to resolve. + /// True if the wildcard resolution should not be attempted. + /// The resolved (absolute) path. + private string ResolveFilePath(string path, bool isLiteralPath) + { + string resolvedPath; + + try + { + ProviderInfo provider = null; + PSDriveInfo drive = null; + + if (isLiteralPath) + { + resolvedPath = SessionState.Path.GetUnresolvedProviderPathFromPSPath(path, out provider, out drive); + } + else + { + Collection filePaths = SessionState.Path.GetResolvedProviderPathFromPSPath(path, out provider); + + if (!provider.Name.Equals("FileSystem", StringComparison.OrdinalIgnoreCase)) + { + ReportFileSystemProviderNotUsed(path); + } + + if (filePaths.Count > 1) + { + ReportMultipleFilesNotSupported(); + } + + resolvedPath = filePaths[0]; + } + } + catch (ItemNotFoundException) + { + resolvedPath = null; + } + + return resolvedPath; + } + + /// + /// Throws terminating error for not using file system provider. + /// + /// The path to report. + private void ReportFileSystemProviderNotUsed(string path) + { + var errorMessage = string.Format(CultureInfo.CurrentCulture, Resources.OnlySupportsFileSystemPaths, path); + var exception = new ArgumentException(errorMessage); + var errorRecord = new ErrorRecord(exception, "OnlySupportsFileSystemPaths", ErrorCategory.InvalidArgument, path); + ThrowTerminatingError(errorRecord); + } + + /// + /// Throws terminating error for path not found. + /// + /// The path to report. + private void ReportPathNotFound(string path) + { + var errorMessage = string.Format(CultureInfo.CurrentCulture, Resources.PathNotFound, path); + var exception = new ArgumentException(errorMessage); + var errorRecord = new ErrorRecord(exception, "PathNotFound", ErrorCategory.InvalidArgument, path); + ThrowTerminatingError(errorRecord); + } + + /// + /// Throws terminating error for multiple files being used. + /// + private void ReportMultipleFilesNotSupported() + { + var errorMessage = string.Format(CultureInfo.CurrentCulture, Resources.MultipleFilesNotSupported); + var exception = new ArgumentException(errorMessage); + var errorRecord = new ErrorRecord(exception, "MultipleFilesNotSupported", ErrorCategory.InvalidArgument, null); + ThrowTerminatingError(errorRecord); + } + + /// + /// Gets encoding for path. + /// + /// The path to get file encoding. + /// The encoding of file. + private 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/Microsoft.PowerShell.TextUtility.csproj b/src/code/Microsoft.PowerShell.TextUtility.csproj index ad59249..3828123 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 + Resources.resx + + + + + + ResXFileCodeGenerator + Resources.Designer.cs + + + diff --git a/src/code/Properties/Resources.Designer.cs b/src/code/Properties/Resources.Designer.cs new file mode 100644 index 0000000..4495e52 --- /dev/null +++ b/src/code/Properties/Resources.Designer.cs @@ -0,0 +1,90 @@ +//------------------------------------------------------------------------------ +// +// 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.Properties { + 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 Resources { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() { + } + + /// + /// 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.Properties.Resources", typeof(Resources).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/Properties/Resources.resx b/src/code/Properties/Resources.resx new file mode 100644 index 0000000..23ee479 --- /dev/null +++ b/src/code/Properties/Resources.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..b634413 --- /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.Commands.GetFileEncodingCommand' + } + + It "Should throw exception if path is not found using -LiteralPath" { + { Get-FileEncoding -LiteralPath nonexistentpath } | Should -Throw -ErrorId 'PathNotFound,Microsoft.PowerShell.Commands.GetFileEncodingCommand' + } + + It "Should throw exception if path is not file system path" { + { Get-FileEncoding -Path 'Cert:\CurrentUser\My' } | Should -Throw -ErrorId 'OnlySupportsFileSystemPaths,Microsoft.PowerShell.Commands.GetFileEncodingCommand' + } + + It "Should throw exception if multiple paths is specified" { + { Get-FileEncoding -Path '*' } | Should -Throw -ErrorId 'MultipleFilesNotSupported,Microsoft.PowerShell.Commands.GetFileEncodingCommand' + } +} \ No newline at end of file From 3ef2fc8364734db474577de24de00c8890fcd014 Mon Sep 17 00:00:00 2001 From: ArmaanMcleod Date: Sat, 4 Nov 2023 22:46:49 +1100 Subject: [PATCH 2/9] Fix test for file system provider --- test/Get-FileEncoding.Tests.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/Get-FileEncoding.Tests.ps1 b/test/Get-FileEncoding.Tests.ps1 index b634413..e179ea9 100644 --- a/test/Get-FileEncoding.Tests.ps1 +++ b/test/Get-FileEncoding.Tests.ps1 @@ -43,7 +43,7 @@ Describe "Get-FileEncoding" -Tags "CI" { } It "Should throw exception if path is not file system path" { - { Get-FileEncoding -Path 'Cert:\CurrentUser\My' } | Should -Throw -ErrorId 'OnlySupportsFileSystemPaths,Microsoft.PowerShell.Commands.GetFileEncodingCommand' + { Get-FileEncoding -Path 'Env:' } | Should -Throw -ErrorId 'OnlySupportsFileSystemPaths,Microsoft.PowerShell.Commands.GetFileEncodingCommand' } It "Should throw exception if multiple paths is specified" { From b8aaf3247b269b529d23d4204c2ca53f497fd3d3 Mon Sep 17 00:00:00 2001 From: ArmaanMcleod Date: Sat, 4 Nov 2023 22:58:21 +1100 Subject: [PATCH 3/9] Change name of file system error reporting method --- src/code/GetFileEncodingCommand.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/code/GetFileEncodingCommand.cs b/src/code/GetFileEncodingCommand.cs index b112982..d793533 100644 --- a/src/code/GetFileEncodingCommand.cs +++ b/src/code/GetFileEncodingCommand.cs @@ -96,7 +96,7 @@ private string ResolveFilePath(string path, bool isLiteralPath) if (!provider.Name.Equals("FileSystem", StringComparison.OrdinalIgnoreCase)) { - ReportFileSystemProviderNotUsed(path); + ReportOnlySupportsFileSystemPaths(path); } if (filePaths.Count > 1) @@ -119,7 +119,7 @@ private string ResolveFilePath(string path, bool isLiteralPath) /// Throws terminating error for not using file system provider. /// /// The path to report. - private void ReportFileSystemProviderNotUsed(string path) + private void ReportOnlySupportsFileSystemPaths(string path) { var errorMessage = string.Format(CultureInfo.CurrentCulture, Resources.OnlySupportsFileSystemPaths, path); var exception = new ArgumentException(errorMessage); From c06d1a96ead21facc60a360b1c3c6cb37c7ec331 Mon Sep 17 00:00:00 2001 From: ArmaanMcleod Date: Sun, 5 Nov 2023 09:54:37 +1100 Subject: [PATCH 4/9] Move path helper method to PathUtils and fix namespace --- src/code/GetFileEncodingCommand.cs | 105 ++------------------------- src/code/PathUtils.cs | 109 +++++++++++++++++++++++++++++ test/Get-FileEncoding.Tests.ps1 | 8 +-- 3 files changed, 117 insertions(+), 105 deletions(-) create mode 100644 src/code/PathUtils.cs diff --git a/src/code/GetFileEncodingCommand.cs b/src/code/GetFileEncodingCommand.cs index d793533..5fee905 100644 --- a/src/code/GetFileEncodingCommand.cs +++ b/src/code/GetFileEncodingCommand.cs @@ -1,15 +1,11 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System; -using System.Collections.ObjectModel; -using System.Globalization; using System.IO; using System.Management.Automation; using System.Text; -using Microsoft.PowerShell.TextUtility.Properties; -namespace Microsoft.PowerShell.Commands +namespace Microsoft.PowerShell.TextUtility { /// /// This class implements the Get-FileEncoding command. @@ -61,107 +57,14 @@ public string LiteralPath /// protected override void ProcessRecord() { - string resolvedPath = ResolveFilePath(Path, _isLiteralPath); + string resolvedPath = PathUtils.ResolveFilePath(Path, this, _isLiteralPath); if (!File.Exists(resolvedPath)) { - ReportPathNotFound(Path); + PathUtils.ReportPathNotFound(Path, this); } - WriteObject(GetPathEncoding(resolvedPath)); - } - - /// - /// Resolves user provided path using file system provider. - /// - /// The path to resolve. - /// True if the wildcard resolution should not be attempted. - /// The resolved (absolute) path. - private string ResolveFilePath(string path, bool isLiteralPath) - { - string resolvedPath; - - try - { - ProviderInfo provider = null; - PSDriveInfo drive = null; - - if (isLiteralPath) - { - resolvedPath = SessionState.Path.GetUnresolvedProviderPathFromPSPath(path, out provider, out drive); - } - else - { - Collection filePaths = SessionState.Path.GetResolvedProviderPathFromPSPath(path, out provider); - - if (!provider.Name.Equals("FileSystem", StringComparison.OrdinalIgnoreCase)) - { - ReportOnlySupportsFileSystemPaths(path); - } - - if (filePaths.Count > 1) - { - ReportMultipleFilesNotSupported(); - } - - resolvedPath = filePaths[0]; - } - } - catch (ItemNotFoundException) - { - resolvedPath = null; - } - - return resolvedPath; - } - - /// - /// Throws terminating error for not using file system provider. - /// - /// The path to report. - private void ReportOnlySupportsFileSystemPaths(string path) - { - var errorMessage = string.Format(CultureInfo.CurrentCulture, Resources.OnlySupportsFileSystemPaths, path); - var exception = new ArgumentException(errorMessage); - var errorRecord = new ErrorRecord(exception, "OnlySupportsFileSystemPaths", ErrorCategory.InvalidArgument, path); - ThrowTerminatingError(errorRecord); - } - - /// - /// Throws terminating error for path not found. - /// - /// The path to report. - private void ReportPathNotFound(string path) - { - var errorMessage = string.Format(CultureInfo.CurrentCulture, Resources.PathNotFound, path); - var exception = new ArgumentException(errorMessage); - var errorRecord = new ErrorRecord(exception, "PathNotFound", ErrorCategory.InvalidArgument, path); - ThrowTerminatingError(errorRecord); - } - - /// - /// Throws terminating error for multiple files being used. - /// - private void ReportMultipleFilesNotSupported() - { - var errorMessage = string.Format(CultureInfo.CurrentCulture, Resources.MultipleFilesNotSupported); - var exception = new ArgumentException(errorMessage); - var errorRecord = new ErrorRecord(exception, "MultipleFilesNotSupported", ErrorCategory.InvalidArgument, null); - ThrowTerminatingError(errorRecord); - } - - /// - /// Gets encoding for path. - /// - /// The path to get file encoding. - /// The encoding of file. - private static Encoding GetPathEncoding(string path) - { - using (var reader = new StreamReader(path, Encoding.Default, detectEncodingFromByteOrderMarks: true)) - { - _ = reader.Read(); - return reader.CurrentEncoding; - } + WriteObject(PathUtils.GetPathEncoding(resolvedPath)); } } } \ No newline at end of file diff --git a/src/code/PathUtils.cs b/src/code/PathUtils.cs new file mode 100644 index 0000000..7c5ad79 --- /dev/null +++ b/src/code/PathUtils.cs @@ -0,0 +1,109 @@ +using System; +using System.Collections.ObjectModel; +using System.Globalization; +using System.IO; +using System.Management.Automation; +using System.Text; +using Microsoft.PowerShell.TextUtility.Properties; + +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. + /// 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; + + if (isLiteralPath) + { + resolvedPath = command.SessionState.Path.GetUnresolvedProviderPathFromPSPath(path, out provider, out drive); + } + else + { + Collection filePaths = command.SessionState.Path.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. + internal static void ReportOnlySupportsFileSystemPaths(string path, PSCmdlet command) + { + var errorMessage = string.Format(CultureInfo.CurrentCulture, Resources.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. + internal static void ReportPathNotFound(string path, PSCmdlet command) + { + var errorMessage = string.Format(CultureInfo.CurrentCulture, Resources.PathNotFound, path); + var exception = new ArgumentException(errorMessage); + var errorRecord = new ErrorRecord(exception, "PathNotFound", ErrorCategory.InvalidArgument, path); + command.ThrowTerminatingError(errorRecord); + } + + /// + /// Throws terminating error for multiple files being used. + /// + internal static void ReportMultipleFilesNotSupported(PSCmdlet command) + { + var errorMessage = string.Format(CultureInfo.CurrentCulture, Resources.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/test/Get-FileEncoding.Tests.ps1 b/test/Get-FileEncoding.Tests.ps1 index e179ea9..10c2a21 100644 --- a/test/Get-FileEncoding.Tests.ps1 +++ b/test/Get-FileEncoding.Tests.ps1 @@ -35,18 +35,18 @@ Describe "Get-FileEncoding" -Tags "CI" { } It "Should throw exception if path is not found using -Path" { - { Get-FileEncoding -Path nonexistentpath } | Should -Throw -ErrorId 'PathNotFound,Microsoft.PowerShell.Commands.GetFileEncodingCommand' + { 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.Commands.GetFileEncodingCommand' + { 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.Commands.GetFileEncodingCommand' + { 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.Commands.GetFileEncodingCommand' + { Get-FileEncoding -Path '*' } | Should -Throw -ErrorId 'MultipleFilesNotSupported,Microsoft.PowerShell.TextUtility.GetFileEncodingCommand' } } \ No newline at end of file From c00c2e037b1150ffb4bfcff01d332bcf7488f4c6 Mon Sep 17 00:00:00 2001 From: ArmaanMcleod Date: Sun, 5 Nov 2023 09:58:07 +1100 Subject: [PATCH 5/9] Update README --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index ff8346c..5b0408e 100644 --- a/README.md +++ b/README.md @@ -19,3 +19,7 @@ Return a base64 encoded representation of a string. ## ConvertFrom-TextTable This will convert tabular data and convert it to an object. + +## Get-FileEncoding + +This cmdlet returns encoding for a file. From 9a9866ccfa581643a6f457ea41e0c66aad9b9d00 Mon Sep 17 00:00:00 2001 From: ArmaanMcleod Date: Sun, 5 Nov 2023 19:03:20 +1100 Subject: [PATCH 6/9] Use ErrorCategory.ObjectNotFound for path not found error category --- src/code/PathUtils.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/code/PathUtils.cs b/src/code/PathUtils.cs index 7c5ad79..f5a19d9 100644 --- a/src/code/PathUtils.cs +++ b/src/code/PathUtils.cs @@ -77,7 +77,7 @@ internal static void ReportPathNotFound(string path, PSCmdlet command) { var errorMessage = string.Format(CultureInfo.CurrentCulture, Resources.PathNotFound, path); var exception = new ArgumentException(errorMessage); - var errorRecord = new ErrorRecord(exception, "PathNotFound", ErrorCategory.InvalidArgument, path); + var errorRecord = new ErrorRecord(exception, "PathNotFound", ErrorCategory.ObjectNotFound, path); command.ThrowTerminatingError(errorRecord); } From 90f8875dffd1043cfe4763db6f47a2452760d28d Mon Sep 17 00:00:00 2001 From: ArmaanMcleod Date: Sun, 5 Nov 2023 19:18:31 +1100 Subject: [PATCH 7/9] Renamed namespace and resx files --- .../Microsoft.PowerShell.TextUtility.csproj | 8 +-- src/code/PathUtils.cs | 8 +-- .../PathUtilityStrings.Designer.cs} | 67 ++++++++++++------- .../PathUtilityStrings.resx} | 0 4 files changed, 49 insertions(+), 34 deletions(-) rename src/code/{Properties/Resources.Designer.cs => Resources/PathUtilityStrings.Designer.cs} (81%) rename src/code/{Properties/Resources.resx => Resources/PathUtilityStrings.resx} (100%) diff --git a/src/code/Microsoft.PowerShell.TextUtility.csproj b/src/code/Microsoft.PowerShell.TextUtility.csproj index 3828123..9102fe6 100644 --- a/src/code/Microsoft.PowerShell.TextUtility.csproj +++ b/src/code/Microsoft.PowerShell.TextUtility.csproj @@ -29,17 +29,17 @@ - + True True - Resources.resx + PathUtilityStrings.resx - + ResXFileCodeGenerator - Resources.Designer.cs + PathUtilityStrings.Designer.cs diff --git a/src/code/PathUtils.cs b/src/code/PathUtils.cs index f5a19d9..a6ca323 100644 --- a/src/code/PathUtils.cs +++ b/src/code/PathUtils.cs @@ -4,7 +4,7 @@ using System.IO; using System.Management.Automation; using System.Text; -using Microsoft.PowerShell.TextUtility.Properties; +using Microsoft.PowerShell.TextUtility.Resources; namespace Microsoft.PowerShell.TextUtility { @@ -63,7 +63,7 @@ internal static string ResolveFilePath(string path, PSCmdlet command, bool isLit /// The path to report. internal static void ReportOnlySupportsFileSystemPaths(string path, PSCmdlet command) { - var errorMessage = string.Format(CultureInfo.CurrentCulture, Resources.OnlySupportsFileSystemPaths, path); + 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); @@ -75,7 +75,7 @@ internal static void ReportOnlySupportsFileSystemPaths(string path, PSCmdlet com /// The path to report. internal static void ReportPathNotFound(string path, PSCmdlet command) { - var errorMessage = string.Format(CultureInfo.CurrentCulture, Resources.PathNotFound, path); + 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); @@ -86,7 +86,7 @@ internal static void ReportPathNotFound(string path, PSCmdlet command) /// internal static void ReportMultipleFilesNotSupported(PSCmdlet command) { - var errorMessage = string.Format(CultureInfo.CurrentCulture, Resources.MultipleFilesNotSupported); + 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); diff --git a/src/code/Properties/Resources.Designer.cs b/src/code/Resources/PathUtilityStrings.Designer.cs similarity index 81% rename from src/code/Properties/Resources.Designer.cs rename to src/code/Resources/PathUtilityStrings.Designer.cs index 4495e52..5179930 100644 --- a/src/code/Properties/Resources.Designer.cs +++ b/src/code/Resources/PathUtilityStrings.Designer.cs @@ -8,10 +8,11 @@ // //------------------------------------------------------------------------------ -namespace Microsoft.PowerShell.TextUtility.Properties { +namespace Microsoft.PowerShell.TextUtility.Resources +{ using System; - - + + /// /// A strongly-typed resource class, for looking up localized strings, etc. /// @@ -22,67 +23,81 @@ namespace Microsoft.PowerShell.TextUtility.Properties { [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")] [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - internal class Resources { - + 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 Resources() { + 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.Properties.Resources", typeof(Resources).Assembly); + 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 { + internal static global::System.Globalization.CultureInfo Culture + { + get + { return resourceCulture; } - set { + 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 { + 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 { + 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 { + internal static string PathNotFound + { + get + { return ResourceManager.GetString("PathNotFound", resourceCulture); } } diff --git a/src/code/Properties/Resources.resx b/src/code/Resources/PathUtilityStrings.resx similarity index 100% rename from src/code/Properties/Resources.resx rename to src/code/Resources/PathUtilityStrings.resx From 62e58aa30a1732bbd675f7d633fbccc8b53da375 Mon Sep 17 00:00:00 2001 From: ArmaanMcleod Date: Sun, 5 Nov 2023 19:21:51 +1100 Subject: [PATCH 8/9] Fix typo --- src/code/Resources/PathUtilityStrings.Designer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/code/Resources/PathUtilityStrings.Designer.cs b/src/code/Resources/PathUtilityStrings.Designer.cs index 5179930..5974d58 100644 --- a/src/code/Resources/PathUtilityStrings.Designer.cs +++ b/src/code/Resources/PathUtilityStrings.Designer.cs @@ -45,7 +45,7 @@ internal PathUtilityStrings() { if (object.ReferenceEquals(resourceMan, null)) { - global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Microsoft.PowerShell.TextUtility.resources.PathUtilityStrings", typeof(PathUtilityStrings).Assembly); + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Microsoft.PowerShell.TextUtility.Resources.PathUtilityStrings", typeof(PathUtilityStrings).Assembly); resourceMan = temp; } return resourceMan; From 2a8dc39a8dae698224af92add544035d53652a40 Mon Sep 17 00:00:00 2001 From: ArmaanMcleod Date: Sun, 5 Nov 2023 19:41:57 +1100 Subject: [PATCH 9/9] Included command in XML docs and make session state path variable --- src/code/PathUtils.cs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/code/PathUtils.cs b/src/code/PathUtils.cs index a6ca323..3e91b40 100644 --- a/src/code/PathUtils.cs +++ b/src/code/PathUtils.cs @@ -17,6 +17,7 @@ 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) @@ -28,13 +29,15 @@ internal static string ResolveFilePath(string path, PSCmdlet command, bool isLit ProviderInfo provider = null; PSDriveInfo drive = null; + PathIntrinsics sessionStatePath = command.SessionState.Path; + if (isLiteralPath) { - resolvedPath = command.SessionState.Path.GetUnresolvedProviderPathFromPSPath(path, out provider, out drive); + resolvedPath = sessionStatePath.GetUnresolvedProviderPathFromPSPath(path, out provider, out drive); } else { - Collection filePaths = command.SessionState.Path.GetResolvedProviderPathFromPSPath(path, out provider); + Collection filePaths = sessionStatePath.GetResolvedProviderPathFromPSPath(path, out provider); if (!provider.Name.Equals("FileSystem", StringComparison.OrdinalIgnoreCase)) { @@ -61,6 +64,7 @@ internal static string ResolveFilePath(string path, PSCmdlet command, bool isLit /// 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); @@ -73,6 +77,7 @@ internal static void ReportOnlySupportsFileSystemPaths(string path, PSCmdlet com /// 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); @@ -84,6 +89,7 @@ internal static void ReportPathNotFound(string path, PSCmdlet command) /// /// Throws terminating error for multiple files being used. /// + /// The command. internal static void ReportMultipleFilesNotSupported(PSCmdlet command) { var errorMessage = string.Format(CultureInfo.CurrentCulture, PathUtilityStrings.MultipleFilesNotSupported);