From cb72a3eba066baa2a4a9e57a1559e027d28f10c5 Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Sat, 27 Jan 2024 16:17:23 +0100 Subject: [PATCH] Get-PSModulePath: Improve the command with Scope (#120) --- CHANGELOG.md | 12 ++ source/Public/Get-PSModulePath.ps1 | 161 +++++++++++++---- tests/Unit/Public/Get-PSModulePath.Tests.ps1 | 172 ++++++++++++++++--- 3 files changed, 289 insertions(+), 56 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 116bead..0e54c9d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Added + +- `Get-PSModulePath` + - Can now return the individual module path for different scopes when + using the parameter `-Scope`. If no parameter is specified the command + return the path for the scope CurrentUser. + +### Fixed + +- `Get-PSModulePath` + - Was using the wrong path separator on Linux and macOS. + ## [0.17.0] - 2024-01-23 ### Added diff --git a/source/Public/Get-PSModulePath.ps1 b/source/Public/Get-PSModulePath.ps1 index 3373c75..2f61e5b 100644 --- a/source/Public/Get-PSModulePath.ps1 +++ b/source/Public/Get-PSModulePath.ps1 @@ -1,19 +1,46 @@ <# .SYNOPSIS - Returns the environment variable PSModulePath from the specified target. + Returns the individual scope path or the environment variable PSModulePath + from one or more of the specified targets. .DESCRIPTION - Returns the environment variable PSModulePath from the specified target. - If more than one target is provided the return will contain all the - concatenation of all unique paths from the targets. If there are no paths - to return the command will return an empty string. + Returns the individual scope path or the environment variable PSModulePath + from one or more of the specified targets. + + If more than one target is provided in the parameter FromTarget the return + value will contain the concatenation of all unique paths from the targets. + If there are no paths to return the command will return an empty string. .PARAMETER FromTarget - Specifies the target to get the PSModulePath from. + Specifies the environment target to get the PSModulePath from. + + .PARAMETER Scope + Specifies the scope to get the individual module path of. .OUTPUTS System.String + .EXAMPLE + Get-PSModulePath + + Returns the module path to the CurrentUser scope. + + .EXAMPLE + Get-PSModulePath -Scope 'CurrentUser' + + Returns the module path to the CurrentUser scope. + + .EXAMPLE + Get-PSModulePath -Scope 'AllUsers' + + Returns the module path to the AllUsers scope. + + .EXAMPLE + Get-PSModulePath -Scope 'Builtin' + + Returns the module path to the Builtin scope. This is the module path + containing the modules that ship with PowerShell. + .EXAMPLE Get-PSModulePath -FromTarget 'Session' @@ -31,49 +58,115 @@ #> function Get-PSModulePath { - [CmdletBinding()] + [CmdletBinding(DefaultParameterSetName = 'Scope')] [OutputType([System.String])] param ( - [Parameter(Mandatory = $true)] + [Parameter(Mandatory = $true, ParameterSetName = 'FromTarget')] [ValidateSet('Session', 'User', 'Machine')] [System.String[]] - $FromTarget - ) + $FromTarget, - $modulePathSession = $modulePathUser = $modulePathMachine = $null + [Parameter(ParameterSetName = 'Scope')] + [ValidateSet('CurrentUser', 'AllUsers', 'Builtin')] + [System.String] + $Scope = 'CurrentUser' + ) - <# - Get the environment variables from required targets. The value returned - is cast to System.String to convert $null values to empty string. - #> - switch ($FromTarget) + if ($PSCmdlet.ParameterSetName -eq 'FromTarget') { - 'Session' - { - $modulePathSession = Get-EnvironmentVariable -Name 'PSModulePath' - } + $modulePathSession = $modulePathUser = $modulePathMachine = $null - 'User' + <# + Get the environment variables from required targets. The value returned + is cast to System.String to convert $null values to empty string. + #> + switch ($FromTarget) { - $modulePathUser = Get-EnvironmentVariable -Name 'PSModulePath' -FromTarget 'User' - } + 'Session' + { + $modulePathSession = Get-EnvironmentVariable -Name 'PSModulePath' -FromTarget 'Session' - 'Machine' - { - $modulePathMachine = Get-EnvironmentVariable -Name 'PSModulePath' -FromTarget 'Machine' + continue + } + + 'User' + { + $modulePathUser = Get-EnvironmentVariable -Name 'PSModulePath' -FromTarget 'User' + + continue + } + + 'Machine' + { + $modulePathMachine = Get-EnvironmentVariable -Name 'PSModulePath' -FromTarget 'Machine' + + continue + } } - } - $modulePath = $modulePathSession, $modulePathUser, $modulePathMachine -join ';' + $modulePath = $modulePathSession, $modulePathUser, $modulePathMachine -join [System.IO.Path]::PathSeparator - $modulePathArray = $modulePath -split ';' | - Where-Object -FilterScript { - -not [System.String]::IsNullOrEmpty($_) - } | - Sort-Object -Unique + $modulePathArray = $modulePath -split [System.IO.Path]::PathSeparator | + Where-Object -FilterScript { + -not [System.String]::IsNullOrEmpty($_) + } | + Sort-Object -Unique + + $modulePath = $modulePathArray -join [System.IO.Path]::PathSeparator + } - $modulePath = $modulePathArray -join ';' + if ($PSCmdlet.ParameterSetName -eq 'Scope') + { + switch ($Scope) + { + 'CurrentUser' + { + $modulePath = if ($IsLinux -or $IsMacOS) + { + # Must be correct case on case-sensitive file systems. + Join-Path -Path $HOME -ChildPath '.local/share/powershell/Modules' + } + else + { + $documentsFolder = [Environment]::GetFolderPath('MyDocuments') + + if ($IsCoreCLR) + { + Join-Path -Path $documentsFolder -ChildPath 'PowerShell/Modules' + } + else + { + Join-Path -Path $documentsFolder -ChildPath 'WindowsPowerShell/Modules' + } + } + + break + } + + 'AllUsers' + { + $modulePath = if ($IsLinux -or $IsMacOS) + { + '/usr/local/share/powershell/Modules' + } + else + { + Join-Path -Path $env:ProgramFiles -ChildPath 'WindowsPowerShell/Modules' + } + + break + } + + 'BuiltIn' + { + # cSPell: ignore PSHOME + $modulePath = Join-Path -Path $PSHOME -ChildPath 'Modules' + + break + } + } + } return $modulePath } diff --git a/tests/Unit/Public/Get-PSModulePath.Tests.ps1 b/tests/Unit/Public/Get-PSModulePath.Tests.ps1 index 6312927..e8fefd9 100644 --- a/tests/Unit/Public/Get-PSModulePath.Tests.ps1 +++ b/tests/Unit/Public/Get-PSModulePath.Tests.ps1 @@ -46,41 +46,169 @@ AfterAll { } Describe 'Get-PSModulePath' { - BeforeAll { - Mock -CommandName Get-EnvironmentVariable -MockWith { - return '/tmp/path' + Context 'When using parameter FromTarget' { + Context 'When returning unique path' { + BeforeAll { + Mock -CommandName Get-EnvironmentVariable -MockWith { + return '/tmp/path' + } + } + + Context 'When getting the PSModulePath session environment variable' { + It 'Should return the correct path' { + Get-PSModulePath -FromTarget 'Session' | Should -Be '/tmp/path' + + Should -Invoke -CommandName Get-EnvironmentVariable -Exactly -Times 1 -Scope It + } + } + + Context 'When getting the PSModulePath user environment variable' { + It 'Should return the correct path' { + Get-PSModulePath -FromTarget 'User' | Should -Be '/tmp/path' + + Should -Invoke -CommandName Get-EnvironmentVariable -Exactly -Times 1 -Scope It + } + } + + Context 'When getting the PSModulePath machine environment variable' { + It 'Should return the correct path' { + Get-PSModulePath -FromTarget 'Machine' | Should -Be '/tmp/path' + + Should -Invoke -CommandName Get-EnvironmentVariable -Exactly -Times 1 -Scope It + } + } + + Context 'When getting the PSModulePath machine environment variable' { + It 'Should return the correct unique path' { + Get-PSModulePath -FromTarget 'Machine', 'User', 'Session' | Should -Be '/tmp/path' + + Should -Invoke -CommandName Get-EnvironmentVariable -Exactly -Times 3 -Scope It + } + } } - } - Context 'When getting the PSModulePath session environment variable' { - It 'Should return the correct path' { - Get-PSModulePath -FromTarget 'Session' | Should -Be '/tmp/path' + Context 'When returning different paths from scopes' { + BeforeAll { + Mock -CommandName Get-EnvironmentVariable -MockWith { + switch ($PesterBoundParameters.FromTarget) + { + 'Session' + { + return '/tmp/session_path' + } + + 'User' + { + return '/tmp/user_path' + } + + 'Machine' + { + return '/tmp/machine_path' + } + + default + { + return $null + } + } + } + } + + Context 'When getting the PSModulePath from Session' { + It 'Should return the correct session path' { + Get-PSModulePath -FromTarget 'Session' | Should -Be '/tmp/session_path' + + Should -Invoke -CommandName Get-EnvironmentVariable -ParameterFilter { + $FromTarget -eq 'Session' + } -Exactly -Times 1 -Scope It + } + } + + Context 'When getting the PSModulePath from User' { + It 'Should return the correct user path' { + Get-PSModulePath -FromTarget 'User' | Should -Be '/tmp/user_path' + + Should -Invoke -CommandName Get-EnvironmentVariable -ParameterFilter { + $FromTarget -eq 'User' + } -Exactly -Times 1 -Scope It + } + } + + Context 'When getting the PSModulePath from Machine' { + It 'Should return the correct machine path' { + Get-PSModulePath -FromTarget 'Machine' | Should -Be '/tmp/machine_path' - Should -Invoke -CommandName Get-EnvironmentVariable -Exactly -Times 1 -Scope It + Should -Invoke -CommandName Get-EnvironmentVariable -ParameterFilter { + $FromTarget -eq 'Machine' + } -Exactly -Times 1 -Scope It + } + } + + Context 'When getting the PSModulePath from all targets' { + It 'Should return the correct concatenated path' { + $result = Get-PSModulePath -FromTarget 'Session', 'User', 'Machine' + + $result | Should -Match '/tmp/session_path' + $result | Should -Match '/tmp/user_path' + $result | Should -Match '/tmp/machine_path' + + Should -Invoke -CommandName Get-EnvironmentVariable -Exactly -Times 3 -Scope It + } + + It 'Should have the correct path separator in the result' { + $result = Get-PSModulePath -FromTarget 'Session', 'User', 'Machine' + + $result | Should -Match ([System.Text.RegularExpressions.Regex]::Escape([System.IO.Path]::PathSeparator)) + + Should -Invoke -CommandName Get-EnvironmentVariable -Exactly -Times 3 -Scope It + } + } } } - Context 'When getting the PSModulePath user environment variable' { - It 'Should return the correct path' { - Get-PSModulePath -FromTarget 'User' | Should -Be '/tmp/path' + Context 'When using parameter Scope' { + Context 'When Scope is CurrentUser' { + It 'Should return correct path on Linux or MacOS' -Skip:($IsWindows -or -not $IsCoreCLR) { + $result = Get-PSModulePath -Scope 'CurrentUser' + + $result | Should -Be (Join-Path -Path $HOME -ChildPath '.local/share/powershell/Modules') + } + + It 'Should return correct path on Windows with CoreCLR' -Skip:($IsLinux -or $IsMacOS -or -not $IsCoreCLR) { + $result = Get-PSModulePath -Scope 'CurrentUser' - Should -Invoke -CommandName Get-EnvironmentVariable -Exactly -Times 1 -Scope It + $result | Should -Be ([Environment]::GetFolderPath('MyDocuments') | Join-Path -ChildPath 'PowerShell' | Join-Path -ChildPath 'Modules') + } + + It 'Should return correct path on Windows without CoreCLR' -Skip:($IsLinux -or $IsMacOS -or $IsCoreCLR) { + $result = Get-PSModulePath -Scope 'CurrentUser' + + $result | Should -Be ([Environment]::GetFolderPath('MyDocuments') | Join-Path -ChildPath 'WindowsPowerShell' | Join-Path -ChildPath 'Modules') + } } - } - Context 'When getting the PSModulePath machine environment variable' { - It 'Should return the correct path' { - Get-PSModulePath -FromTarget 'Machine' | Should -Be '/tmp/path' + Context 'When Scope is AllUsers' { + It 'Should return correct path on Linux or MacOS' -Skip:($IsWindows -or -not $IsCoreCLR) { + $result = Get-PSModulePath -Scope 'AllUsers' - Should -Invoke -CommandName Get-EnvironmentVariable -Exactly -Times 1 -Scope It + $result | Should -Be '/usr/local/share/powershell/Modules' + } + + It 'Should return correct path on Windows' -Skip:($IsLinux -or $IsMacOS) { + $result = Get-PSModulePath -Scope 'AllUsers' + + $result | Should -Be (Join-Path -Path $env:ProgramFiles -ChildPath 'WindowsPowerShell/Modules') + } } - } - Context 'When getting the PSModulePath machine environment variable' { - It 'Should return the correct unique path' { - Get-PSModulePath -FromTarget 'Machine', 'User', 'Session' | Should -Be '/tmp/path' + Context 'When Scope is BuiltIn' { + It 'Should return correct path' { + $result = Get-PSModulePath -Scope 'BuiltIn' - Should -Invoke -CommandName Get-EnvironmentVariable -Exactly -Times 3 -Scope It + # cSPell: ignore PSHOME + $result | Should -Be (Join-Path -Path $PSHOME -ChildPath 'Modules') + } } } }