Skip to content

Commit 74bbac1

Browse files
trackdJustinGrote
andauthored
✨ Use PowerShell internals for default Destination determination (#90)
ModuleFast now will use PowerShell's internal information to determine the default install path, and it will now be able to respect changes to the `PSModulePath` in `powershell.config.json` and default installation to the specified path. `-Scope` has also been updated with a `AllUsers` option to install to the AllUsers path as provided by PowerShell as well. Closes #89 --------- Co-authored-by: trackd <[email protected]> Co-authored-by: Justin Grote <[email protected]>
1 parent 66269f4 commit 74bbac1

File tree

2 files changed

+61
-38
lines changed

2 files changed

+61
-38
lines changed

ModuleFast.psm1

Lines changed: 46 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ $SCRIPT:DefaultSource = 'https://pwsh.gallery/index.json'
3737

3838
enum InstallScope {
3939
CurrentUser
40+
AllUsers
4041
}
4142

4243

@@ -254,54 +255,50 @@ function Install-ModuleFast {
254255
begin {
255256
trap {$PSCmdlet.ThrowTerminatingError($PSItem)}
256257

257-
# Setup the Destination repository
258-
$defaultRepoPath = $(Join-Path ([Environment]::GetFolderPath('LocalApplicationData')) 'powershell/Modules')
259258

260-
# Get the current PSModulePath
261-
$PSModulePaths = $env:PSModulePath.Split([Path]::PathSeparator, [StringSplitOptions]::RemoveEmptyEntries)
262259

263260
#Clear the ModuleFastCache if -Update is specified to ensure fresh lookups of remote module availability
264261
if ($Update) {
265262
Clear-ModuleFastCache
266263
}
267264

268-
if ($Scope -eq [InstallScope]::CurrentUser) {
269-
$Destination = 'CurrentUser'
265+
$defaultRepoPath = $(Join-Path ([Environment]::GetFolderPath('LocalApplicationData')) 'powershell/Modules')
266+
if (-not $Destination) {
267+
#Special function that will retrieve the default module path for the current user
268+
$Destination = Get-PSDefaultModulePath -AllUsers:($Scope -eq 'AllUsers')
269+
270+
#Special case for Windows to avoid the default installation path because it has issues with OneDrive
271+
$defaultWindowsModulePath = Join-Path ([Environment]::GetFolderPath('MyDocuments')) 'PowerShell/Modules'
272+
if ($IsWindows -and $Destination -eq $defaultWindowsModulePath -and $Scope -ne 'CurrentUser') {
273+
Write-Debug "Windows Documents module folder detected. Changing to $defaultRepoPath"
274+
$Destination = $defaultRepoPath
275+
}
270276
}
277+
271278
if (-not $Destination) {
272-
$Destination = $defaultRepoPath
273-
} elseif ($IsWindows -and $Destination -eq 'CurrentUser') {
274-
$windowsDefaultDocumentsPath = Join-Path ([Environment]::GetFolderPath('MyDocuments')) 'PowerShell/Modules'
275-
$Destination = $windowsDefaultDocumentsPath
276-
# if CurrentUser and is on Windows, we do not need to update the PSModulePath or the user profile.
277-
# this allows for a similar experience to Install-Module and Install-PSResource
278-
$NoPSModulePathUpdate = $true
279-
$NoProfileUpdate = $true
279+
throw 'Failed to determine destination path. This is a bug, please report it, it should always have something by this point.'
280280
}
281281

282-
# Autocreate the default as a convenience, otherwise require the path to be present to avoid mistakes
283-
if ($Destination -eq $defaultRepoPath -and -not (Test-Path $Destination)) {
284-
if (Approve-Action 'Create Destination Folder' $Destination) {
282+
# Require approval to create the destination folder if it is not our default path, otherwise this is automatic
283+
if (-not (Test-Path $Destination)) {
284+
if ($configRepoPath -or
285+
$Destination -eq $defaultRepoPath -or
286+
(Approve-Action 'Create Destination Folder' $Destination)
287+
) {
285288
New-Item -ItemType Directory -Path $Destination -Force | Out-Null
286289
}
287290
}
288291

289292
$Destination = Resolve-Path $Destination
290293

291294
if (-not $NoPSModulePathUpdate) {
292-
if ($defaultRepoPath -ne $Destination -and $Destination -notin $PSModulePaths) {
293-
Write-Warning 'Parameter -Destination is set to a custom path not in your current PSModulePath. We will add it to your PSModulePath for this session. You can suppress this behavior with the -NoPSModulePathUpdate switch.'
294-
$NoProfileUpdate = $true
295-
}
295+
# Get the current PSModulePath
296+
$PSModulePaths = $env:PSModulePath.Split([Path]::PathSeparator, [StringSplitOptions]::RemoveEmptyEntries)
296297

297-
$addToPathParams = @{
298-
Destination = $Destination
299-
NoProfileUpdate = $NoProfileUpdate
300-
}
301-
if ($PSBoundParameters.ContainsKey('Confirm')) {
302-
$addToPathParams.Confirm = $PSBoundParameters.Confirm
298+
#Only update if the module path is not already in the PSModulePath
299+
if ($Destination -notin $PSModulePaths) {
300+
Add-DestinationToPSModulePath -Destination $Destination -NoProfileUpdate:$NoProfileUpdate -Confirm:$Confirm
303301
}
304-
Add-DestinationToPSModulePath @addtoPathParams
305302
}
306303

307304
#We want to maintain a single HttpClient for the life of the module. This isn't as big of a deal as it used to be but
@@ -2160,6 +2157,27 @@ function Approve-Action {
21602157
return $ThisCmdlet.ShouldProcess($Target, $Action)
21612158
}
21622159

2160+
#Fetches the module path for the current user or all users.
2161+
#HACK: Uses a private API until https://github.com/PowerShell/PowerShell/issues/15552 is resolved
2162+
function Get-PSDefaultModulePath ([Switch]$AllUsers) {
2163+
$scopeType = [Management.Automation.Configuration.ConfigScope]
2164+
$pscType = $scopeType.
2165+
Assembly.
2166+
GetType('System.Management.Automation.Configuration.PowerShellConfig')
2167+
2168+
$pscInstance = $pscType.
2169+
GetField('Instance', [Reflection.BindingFlags]'Static,NonPublic').
2170+
GetValue($null)
2171+
2172+
$getModulePathMethod = $pscType.GetMethod('GetModulePath', [Reflection.BindingFlags]'Instance,NonPublic')
2173+
2174+
if ($AllUsers) {
2175+
$getModulePathMethod.Invoke($pscInstance, $scopeType::AllUsers) ?? [Management.Automation.ModuleIntrinsics]::GetPSModulePath('BuiltIn')
2176+
} else {
2177+
$getModulePathMethod.Invoke($pscInstance, $scopeType::CurrentUser) ?? [Management.Automation.ModuleIntrinsics]::GetPSModulePath('User')
2178+
}
2179+
}
2180+
21632181
#endregion Helpers
21642182

21652183
### ISSUES

ModuleFast.tests.ps1

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -562,19 +562,17 @@ Describe 'Install-ModuleFast' -Tag 'E2E' {
562562

563563
#TODO: Possibly mock this so we don't touch the testing system documents directory
564564
It 'Destination CurrentUser installs to $HOME\Documents\PowerShell\Modules' {
565-
try {
566-
Remove-Item $HOME\Documents\PowerShell\Modules\PrereleaseTest -Recurse -Force -ErrorAction SilentlyContinue
567-
Install-ModuleFast @imfParams 'PrereleaseTest' -Destination CurrentUser
568-
Resolve-Path $HOME\Documents\PowerShell\Modules\PrereleaseTest -EA Stop
569-
} finally {
570-
Remove-Item $HOME\Documents\PowerShell\Modules\PrereleaseTest -Recurse -Force -ErrorAction SilentlyContinue
565+
if (-not $profile) {
566+
Set-ItResult -Skipped -Because 'This test is not supported when $profile is not set'
567+
}
568+
$winConfigPath = Join-Path (Split-Path ($profile.CurrentUserAllHosts)) 'powershell.config.json'
569+
if (-not $IsWindows -or (Test-Path $winConfigPath)) {
570+
Set-ItResult -Skipped -Because 'This test is not supported on non-Windows or when powershell.config.json is present'
571571
}
572-
}
573572

574-
It 'Scope CurrentUser installs to $HOME\Documents\PowerShell\Modules' {
575573
try {
576574
Remove-Item $HOME\Documents\PowerShell\Modules\PrereleaseTest -Recurse -Force -ErrorAction SilentlyContinue
577-
Install-ModuleFast @imfParams 'PrereleaseTest' -Scope CurrentUser
575+
Install-ModuleFast @imfParams 'PrereleaseTest' -Destination CurrentUser
578576
Resolve-Path $HOME\Documents\PowerShell\Modules\PrereleaseTest -EA Stop
579577
} finally {
580578
Remove-Item $HOME\Documents\PowerShell\Modules\PrereleaseTest -Recurse -Force -ErrorAction SilentlyContinue
@@ -764,7 +762,15 @@ Describe 'Install-ModuleFast' -Tag 'E2E' {
764762

765763
Describe 'GitHub Packages' {
766764
It 'Gets Specific Module' {
765+
if (-not (Get-Command Get-Secret -ea 0)) {
766+
Set-ItResult -Skipped -Because 'SecretManagement Not Present'
767+
return
768+
}
767769
$credential = [PSCredential]::new('Pester', (Get-Secret -Name 'ReadOnlyPackagesGithubPAT'))
770+
if (-not $credential) {
771+
Set-ItResult -Skipped -Because 'No ReadOnlyPackagesGithubPAT Credential'
772+
return
773+
}
768774
$actual = Install-ModuleFast @imfParams -Specification 'PrereleaseTest=0.0.1' -Source 'https://nuget.pkg.github.com/justingrote/index.json' -Credential $credential -Plan
769775
$actual.Name | Should -Be 'PrereleaseTest'
770776
$actual.ModuleVersion | Should -Be '0.0.1'
@@ -778,4 +784,3 @@ Describe 'Install-ModuleFast' -Tag 'E2E' {
778784
}
779785
}
780786
}
781-

0 commit comments

Comments
 (0)