diff --git a/Engine/CommandInfoCache.cs b/Engine/CommandInfoCache.cs index 20fd211aa..d41c02dbb 100644 --- a/Engine/CommandInfoCache.cs +++ b/Engine/CommandInfoCache.cs @@ -69,6 +69,10 @@ public CommandInfo GetCommandInfoLegacy(string commandOrAliasName, CommandTypes? /// Returns null if command does not exists private static CommandInfo GetCommandInfoInternal(string cmdName, CommandTypes? commandType) { + // 'Get-Command ?' would return % for example due to PowerShell interpreting is a single-character-wildcard search and not just the ? alias. + // For more details see https://github.com/PowerShell/PowerShell/issues/9308 + cmdName = WildcardPattern.Escape(cmdName); + using (var ps = System.Management.Automation.PowerShell.Create()) { ps.AddCommand("Get-Command") diff --git a/Rules/UseCorrectCasing.cs b/Rules/UseCorrectCasing.cs index 490008b1d..2bfe322cf 100644 --- a/Rules/UseCorrectCasing.cs +++ b/Rules/UseCorrectCasing.cs @@ -5,6 +5,9 @@ using System.Collections.Generic; using System.Management.Automation.Language; using Microsoft.Windows.PowerShell.ScriptAnalyzer.Generic; +using System.Management.Automation; +using System.IO; +using System.Runtime.InteropServices; #if !CORECLR using System.ComponentModel.Composition; #endif @@ -29,6 +32,11 @@ public override IEnumerable AnalyzeScript(Ast ast, string file IEnumerable commandAsts = ast.FindAll(testAst => testAst is CommandAst, true); + bool isWindows = true; +#if CORECLR + isWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows); +#endif + // Iterates all CommandAsts and check the command name. foreach (CommandAst commandAst in commandAsts) { @@ -52,6 +60,12 @@ public override IEnumerable AnalyzeScript(Ast ast, string file var fullyqualifiedName = $"{commandInfo.ModuleName}\\{shortName}"; var isFullyQualified = commandName.Equals(fullyqualifiedName, StringComparison.OrdinalIgnoreCase); var correctlyCasedCommandName = isFullyQualified ? fullyqualifiedName : shortName; + if (isWindows && commandInfo.CommandType == CommandTypes.Application && !Path.HasExtension(commandName)) + { + // For binaries that could exist on both Windows and Linux like e.g. git we do not want to expand + // git to git.exe to keep the script cross-platform compliant + correctlyCasedCommandName = Path.GetFileNameWithoutExtension(correctlyCasedCommandName); + } if (!commandName.Equals(correctlyCasedCommandName, StringComparison.Ordinal)) { @@ -158,4 +172,4 @@ public override string GetSourceName() return string.Format(CultureInfo.CurrentCulture, Strings.SourceName); } } -} \ No newline at end of file +} diff --git a/Tests/Rules/UseCorrectCasing.tests.ps1 b/Tests/Rules/UseCorrectCasing.tests.ps1 index 396413b38..e4ec35e9a 100644 --- a/Tests/Rules/UseCorrectCasing.tests.ps1 +++ b/Tests/Rules/UseCorrectCasing.tests.ps1 @@ -11,6 +11,26 @@ Describe "UseCorrectCasing" { Invoke-Formatter '"$(get-childitem)"' | Should -Be '"$(get-childitem)"' } + It "Corrects alias correctly" { + Invoke-Formatter 'Gci' | Should -Be 'gci' + Invoke-Formatter '?' | Should -Be '?' + } + + It "Corrects applications on Windows to not end in .exe" -Skip:($IsLinux -or $IsMacOS) { + Invoke-Formatter 'Cmd' | Should -Be 'cmd' + Invoke-Formatter 'Cmd' | Should -Be 'cmd' + Invoke-Formatter 'MORE' | Should -Be 'more' + Invoke-Formatter 'WinRM' | Should -Be 'winrm' + Invoke-Formatter 'CertMgr' | Should -Be 'certmgr' + } + + It "Preserves extension of applications on Windows" -Skip:($IsLinux -or $IsMacOS) { + Invoke-Formatter 'Cmd.exe' | Should -Be 'cmd.exe' + Invoke-Formatter 'MORE.com' | Should -Be 'more.com' + Invoke-Formatter 'WinRM.cmd' | Should -Be 'winrm.cmd' + Invoke-Formatter 'CertMgr.MSC' | Should -Be 'certmgr.msc' + } + It "corrects case of script function" { function Invoke-DummyFunction { @@ -18,4 +38,4 @@ Describe "UseCorrectCasing" { } Invoke-Formatter 'invoke-dummyFunction' | Should -Be 'Invoke-DummyFunction' } -} \ No newline at end of file +}