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
+}