Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
99 commits
Select commit Hold shift + click to select a range
7327290
Add `Describe`
guillermooo Aug 9, 2026
f542dbe
Add `BeforeAll`
guillermooo Aug 9, 2026
b4f6c5c
Add test
guillermooo Aug 9, 2026
a8390fe
Add test
guillermooo Aug 9, 2026
2eaf439
Add test
guillermooo Aug 9, 2026
e946e4d
Add test
guillermooo Aug 9, 2026
8448b34
Add new `Context`
guillermooo Aug 9, 2026
231af68
Add `BeforeAll` and `AfterAll`
guillermooo Aug 9, 2026
6b75ee8
Add test
guillermooo Aug 9, 2026
2b52fb6
Add test
guillermooo Aug 9, 2026
6258e72
Review: Avoid Windows-only convention
guillermooo Aug 10, 2026
95e9342
Review: Use `Join-Path`
guillermooo Aug 10, 2026
8dbc54d
Review: Fix `Should -Throw` tests
guillermooo Aug 10, 2026
bb7d1db
Review: Typo
guillermooo Aug 10, 2026
0b6503e
Review: Use dedicated $env:PSModulePath for tests
guillermooo Aug 10, 2026
c6d6a31
Refactor
guillermooo Aug 10, 2026
45075a9
Fix name
guillermooo Aug 10, 2026
0f4e5fb
Refactor: Use single variable for the same data
guillermooo Aug 11, 2026
c56d882
Consistent formatting
guillermooo Aug 11, 2026
8a0a003
Refactor: Consolidate variables
guillermooo Aug 11, 2026
c2bf6cf
Cleanup
guillermooo Aug 11, 2026
7dd5497
Add test
guillermooo Aug 11, 2026
92c622f
Add test
guillermooo Aug 11, 2026
4087b48
Add `Context`
guillermooo Aug 11, 2026
3dbf91e
Add `BeforeAll` and `AfterAll`
guillermooo Aug 11, 2026
dbf0587
Add test
guillermooo Aug 11, 2026
592a4a1
Add test
guillermooo Aug 11, 2026
11edc82
Add test
guillermooo Aug 11, 2026
8e822c6
Add test
guillermooo Aug 11, 2026
f385bcd
Add test
guillermooo Aug 11, 2026
5154898
Add test
guillermooo Aug 11, 2026
8afb65f
Add `Describe`
guillermooo Aug 11, 2026
0d362fe
Add test
guillermooo Aug 11, 2026
5d8168c
Add test
guillermooo Aug 11, 2026
52ec5a0
Add `Context`
guillermooo Aug 11, 2026
f28cb52
Add `BeforeAll` and `AfterAll`
guillermooo Aug 11, 2026
ff1c480
Add test
guillermooo Aug 11, 2026
9620dac
Add test
guillermooo Aug 11, 2026
3a3837b
Add `Context`
guillermooo Aug 9, 2026
f06e705
Add `BeforeAll` and `AfterAll`
guillermooo Aug 11, 2026
6d93261
Add test
guillermooo Aug 11, 2026
c8356e6
Add test
guillermooo Aug 11, 2026
aa7e56d
Add test
guillermooo Aug 11, 2026
d0f5508
Add `Describe`
guillermooo Aug 9, 2026
19c392a
Add test
guillermooo Aug 9, 2026
b75d60e
Add test
guillermooo Aug 9, 2026
1c65362
Add test
guillermooo Aug 9, 2026
f4ea1ea
Add `Context`
guillermooo Aug 9, 2026
2fcbc0c
Add `BeforeAll` and `AfterAll`
guillermooo Aug 9, 2026
eb36cb1
Add test
guillermooo Aug 9, 2026
cfe4855
Add test
guillermooo Aug 9, 2026
0deccbe
Add `Context`
guillermooo Aug 9, 2026
2fec8f5
Add `BeforeAll` and `AfterAll`
guillermooo Aug 9, 2026
2c58864
Add test
guillermooo Aug 9, 2026
e4e1696
Add test
guillermooo Aug 9, 2026
0bb5dda
Add test
guillermooo Aug 9, 2026
c6bdcf8
Add `Describe`
guillermooo Aug 9, 2026
0569427
Add test
guillermooo Aug 9, 2026
7bbd2b6
Add test
guillermooo Aug 9, 2026
f2f491e
Add `Context`
guillermooo Aug 9, 2026
63e403d
Add test
guillermooo Aug 9, 2026
9bff3f0
Add `Context`
guillermooo Aug 11, 2026
bdd49cc
Add test
guillermooo Aug 11, 2026
a48815c
Add test
guillermooo Aug 11, 2026
ebe1ecb
Add test
guillermooo Aug 11, 2026
e37c322
Add `Context`
guillermooo Aug 11, 2026
d9daa53
Add test
guillermooo Aug 11, 2026
963c04d
Add `Context`
guillermooo Aug 11, 2026
8fe0486
Add `Context`
guillermooo Aug 11, 2026
47a6d46
Add test
guillermooo Aug 11, 2026
47e4bad
Add `Context`
guillermooo Aug 11, 2026
8f1beaa
Add test
guillermooo Aug 11, 2026
3d826dd
Add `Context`
guillermooo Aug 11, 2026
ce944e9
Add test
guillermooo Aug 11, 2026
a2062ec
Add `Context`
guillermooo Aug 11, 2026
56c561d
Add test
guillermooo Aug 11, 2026
d099bf6
Add `Context`
guillermooo Aug 11, 2026
1075415
Add test
guillermooo Aug 11, 2026
b7b3126
Add `Context`
guillermooo Aug 11, 2026
2b27c51
Add test
guillermooo Aug 11, 2026
2695735
Add `Context`
guillermooo Aug 11, 2026
c8d5041
Add test
guillermooo Aug 11, 2026
33c6948
Clarify intent
guillermooo Jul 18, 2026
4c0f3dd
Use existing API
guillermooo Jul 18, 2026
1454a6a
Edit
guillermooo Jul 18, 2026
dab0c4e
More explicit comparison
guillermooo Jul 18, 2026
9471ebb
Consistency
guillermooo Jul 18, 2026
3233cef
Rename parameter
guillermooo Jul 18, 2026
aa29c6c
Rename parameter
guillermooo Jul 18, 2026
033f72b
Edit
guillermooo Jul 18, 2026
9aba90a
Add parameter precondition
guillermooo Jul 18, 2026
3abdc84
Add parameter precondition
guillermooo Jul 18, 2026
3f7e3c8
Remove redundant comment
guillermooo Jul 18, 2026
4a3ccdd
Edit documentation
guillermooo Jul 18, 2026
7ca24b8
Edit documentation
guillermooo Jul 18, 2026
29fcdf5
Edit documentation
guillermooo Jul 18, 2026
d85fea0
Format
guillermooo Jul 18, 2026
f10393f
Make old remark visible again
guillermooo Jul 18, 2026
85bc795
Hide old remark from Intellisense
guillermooo Jul 18, 2026
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
Expand Up @@ -724,56 +724,63 @@ internal static bool MatchesModulePath(string modulePath, string requiredPath)
}

/// <summary>
/// Takes the name of a module as used in a module specification
/// and either returns it as a simple name (if it was a simple name)
/// or a fully qualified, PowerShell-resolved path.
/// Takes a module name from a <strong>module specification</strong> and returns a
/// normalized module name.
/// </summary>
/// <param name="moduleName">The name or path of the module from the specification.</param>
/// <param name="basePath">The path to base relative paths off.</param>
/// <remarks>
/// <para>
/// A normalized module name is either:
/// <list type="bullet">
/// <item>A simple module name if <paramref name="moduleNameOrPath"/> was a simple name.</item>
/// <item>A fully qualified, PowerShell-resolved path.</item>
/// <item>
/// A fully qualified path by combination of <paramref name="relativeTo"/> and
/// <paramref name="moduleNameOrPath"/> if PowerShell could not resolve the path.
/// </item>
/// </list>
/// </para>
/// <!-- <para>
/// 2018-11-09 rjmholt:
/// There are several, possibly inconsistent, path handling mechanisms in the module cmdlets.
/// After looking through all of them and seeing they all make some assumptions about their caller
/// I wrote this method. Hopefully we can find a standard path resolution API to settle on.
/// </para> -->
/// </remarks>
/// <param name="moduleNameOrPath">The name or path of the module from the specification.</param>
/// <param name="relativeTo">The path to base relative paths off.</param>
/// <param name="executionContext">The current execution context.</param>
/// <returns>
/// The simple module name if the given one was simple,
/// otherwise a fully resolved, absolute path to the module.
/// A simple module name if <paramref name="moduleNameOrPath"/> was a simple module
/// name, otherwise a fully qualified path to the module.
/// </returns>
/// <remarks>
/// 2018-11-09 rjmholt:
/// There are several, possibly inconsistent, path handling mechanisms
/// in the module cmdlets. After looking through all of them and seeing
/// they all make some assumptions about their caller I wrote this method.
/// Hopefully we can find a standard path resolution API to settle on.
/// </remarks>
internal static string NormalizeModuleName(
string moduleName,
string basePath,
ExecutionContext executionContext)
internal static string NormalizeModuleName(string moduleNameOrPath, string relativeTo, ExecutionContext executionContext)
{
if (moduleName == null)
{
return null;
}
ArgumentNullException.ThrowIfNull(moduleNameOrPath);
ArgumentNullException.ThrowIfNull(relativeTo);

// Check whether the module is a path -- if not, it is a simple name and we just return it.
if (!IsModuleNamePath(moduleName))
if (!IsModuleNamePath(moduleNameOrPath))
{
return moduleName;
return moduleNameOrPath;
}

// Standardize directory separators -- Path.IsPathRooted() will return false for "\path\here" on *nix and for "/path/there" on Windows
moduleName = moduleName.Replace(StringLiterals.AlternatePathSeparator, StringLiterals.DefaultPathSeparator);
// Ensure OS default directory separators because
// - Path.IsPathRooted("\some\path") returns false on *nix, and
// - Path.IsPathRooted("/some/path") return false on Windows.
moduleNameOrPath = moduleNameOrPath.Replace(StringLiterals.AlternatePathSeparator, StringLiterals.DefaultPathSeparator);

// Note: Path.IsFullyQualified("\default\root") is false on Windows, but Path.IsPathRooted returns true
if (!Path.IsPathRooted(moduleName))
// On Windows:
// - Path.IsFullyQualified("\default\root") returns false, but
// - Path.IsPathRooted("\default\root") returns true.
if (!Path.IsPathRooted(moduleNameOrPath))
{
moduleName = Path.Join(basePath, moduleName);
moduleNameOrPath = Path.Join(relativeTo, moduleNameOrPath);
}

// Use the PowerShell filesystem provider to fully resolve the path
// If there is a problem, null could be returned -- so default back to the pre-normalized path
string normalizedPath = ModuleCmdletBase.GetResolvedPath(moduleName, executionContext)?.TrimEnd(StringLiterals.DefaultPathSeparator);
// Resolving the path using ModuleCmdletBase.GetResolvedPath() may rarely return null.
string normalizedPath = ModuleCmdletBase.GetResolvedPath(moduleNameOrPath, executionContext)?.TrimEnd(StringLiterals.DefaultPathSeparator);

// ModuleCmdletBase.GetResolvePath will return null in the unlikely event that it failed.
// If it does, we return the fully qualified path generated before.
return normalizedPath ?? Path.GetFullPath(moduleName);
// If the resolved path is null, just return the fully qualified path generated before.
return normalizedPath ?? Path.GetFullPath(moduleNameOrPath);
}

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -90,24 +90,25 @@ internal static Exception ModuleSpecificationInitHelper(ModuleSpecification modu
{
string field = entry.Key.ToString();

if (field.Equals("ModuleName", StringComparison.OrdinalIgnoreCase))
if (field.EqualsOrdinalIgnoreCase("ModuleName"))
{
moduleSpecification.Name = LanguagePrimitives.ConvertTo<string>(entry.Value);
}
else if (field.Equals("ModuleVersion", StringComparison.OrdinalIgnoreCase))
else if (field.EqualsOrdinalIgnoreCase("ModuleVersion"))
{
moduleSpecification.Version = LanguagePrimitives.ConvertTo<Version>(entry.Value);
}
else if (field.Equals("RequiredVersion", StringComparison.OrdinalIgnoreCase))
else if (field.EqualsOrdinalIgnoreCase("RequiredVersion"))
{
moduleSpecification.RequiredVersion = LanguagePrimitives.ConvertTo<Version>(entry.Value);
}
else if (field.Equals("MaximumVersion", StringComparison.OrdinalIgnoreCase))
else if (field.EqualsOrdinalIgnoreCase("MaximumVersion"))
{
moduleSpecification.MaximumVersion = LanguagePrimitives.ConvertTo<string>(entry.Value);
ModuleCmdletBase.GetMaximumVersion(moduleSpecification.MaximumVersion);
// Ensure max version is correctly formatted.
_ = ModuleCmdletBase.GetMaximumVersion(moduleSpecification.MaximumVersion);
}
else if (field.Equals("GUID", StringComparison.OrdinalIgnoreCase))
else if (field.EqualsOrdinalIgnoreCase("GUID"))
{
moduleSpecification.Guid = LanguagePrimitives.ConvertTo<Guid?>(entry.Value);
}
Expand Down Expand Up @@ -339,7 +340,7 @@ internal ModuleSpecification WithNormalizedName(ExecutionContext context, string
}

/// <summary>
/// Compares two ModuleSpecification objects for equality.
/// Compares two <see cref="ModuleSpecification"/> objects for structural equality.
/// </summary>
internal class ModuleSpecificationComparer : IEqualityComparer<ModuleSpecification>
{
Expand All @@ -351,7 +352,7 @@ internal class ModuleSpecificationComparer : IEqualityComparer<ModuleSpecificati
/// <returns>True if the specifications are equal, false otherwise.</returns>
public bool Equals(ModuleSpecification x, ModuleSpecification y)
{
if (x == y)
if (ReferenceEquals(x, y))
{
return true;
}
Expand All @@ -361,7 +362,7 @@ public bool Equals(ModuleSpecification x, ModuleSpecification y)
&& Guid.Equals(x.Guid, y.Guid)
&& Version.Equals(x.RequiredVersion, y.RequiredVersion)
&& Version.Equals(x.Version, y.Version)
&& string.Equals(x.MaximumVersion, y.MaximumVersion);
&& string.Equals(x.MaximumVersion, y.MaximumVersion, StringComparison.OrdinalIgnoreCase);
}

/// <summary>
Expand Down
Loading