Skip to content

Commit

Permalink
✨ Use PowerShell internals for default Destination determination (#90)
Browse files Browse the repository at this point in the history
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 <trackd@users.noreply.github.com>
Co-authored-by: Justin Grote <JustinGrote@users.noreply.github.com>
  • Loading branch information
3 people authored Sep 4, 2024
1 parent 66269f4 commit 74bbac1
Show file tree
Hide file tree
Showing 2 changed files with 61 additions and 38 deletions.
74 changes: 46 additions & 28 deletions ModuleFast.psm1
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ $SCRIPT:DefaultSource = 'https://pwsh.gallery/index.json'

enum InstallScope {
CurrentUser
AllUsers
}


Expand Down Expand Up @@ -254,54 +255,50 @@ function Install-ModuleFast {
begin {
trap {$PSCmdlet.ThrowTerminatingError($PSItem)}

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

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

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

if ($Scope -eq [InstallScope]::CurrentUser) {
$Destination = 'CurrentUser'
$defaultRepoPath = $(Join-Path ([Environment]::GetFolderPath('LocalApplicationData')) 'powershell/Modules')
if (-not $Destination) {
#Special function that will retrieve the default module path for the current user
$Destination = Get-PSDefaultModulePath -AllUsers:($Scope -eq 'AllUsers')

#Special case for Windows to avoid the default installation path because it has issues with OneDrive
$defaultWindowsModulePath = Join-Path ([Environment]::GetFolderPath('MyDocuments')) 'PowerShell/Modules'
if ($IsWindows -and $Destination -eq $defaultWindowsModulePath -and $Scope -ne 'CurrentUser') {
Write-Debug "Windows Documents module folder detected. Changing to $defaultRepoPath"
$Destination = $defaultRepoPath
}
}

if (-not $Destination) {
$Destination = $defaultRepoPath
} elseif ($IsWindows -and $Destination -eq 'CurrentUser') {
$windowsDefaultDocumentsPath = Join-Path ([Environment]::GetFolderPath('MyDocuments')) 'PowerShell/Modules'
$Destination = $windowsDefaultDocumentsPath
# if CurrentUser and is on Windows, we do not need to update the PSModulePath or the user profile.
# this allows for a similar experience to Install-Module and Install-PSResource
$NoPSModulePathUpdate = $true
$NoProfileUpdate = $true
throw 'Failed to determine destination path. This is a bug, please report it, it should always have something by this point.'
}

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

$Destination = Resolve-Path $Destination

if (-not $NoPSModulePathUpdate) {
if ($defaultRepoPath -ne $Destination -and $Destination -notin $PSModulePaths) {
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.'
$NoProfileUpdate = $true
}
# Get the current PSModulePath
$PSModulePaths = $env:PSModulePath.Split([Path]::PathSeparator, [StringSplitOptions]::RemoveEmptyEntries)

$addToPathParams = @{
Destination = $Destination
NoProfileUpdate = $NoProfileUpdate
}
if ($PSBoundParameters.ContainsKey('Confirm')) {
$addToPathParams.Confirm = $PSBoundParameters.Confirm
#Only update if the module path is not already in the PSModulePath
if ($Destination -notin $PSModulePaths) {
Add-DestinationToPSModulePath -Destination $Destination -NoProfileUpdate:$NoProfileUpdate -Confirm:$Confirm
}
Add-DestinationToPSModulePath @addtoPathParams
}

#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
Expand Down Expand Up @@ -2160,6 +2157,27 @@ function Approve-Action {
return $ThisCmdlet.ShouldProcess($Target, $Action)
}

#Fetches the module path for the current user or all users.
#HACK: Uses a private API until https://github.com/PowerShell/PowerShell/issues/15552 is resolved
function Get-PSDefaultModulePath ([Switch]$AllUsers) {
$scopeType = [Management.Automation.Configuration.ConfigScope]
$pscType = $scopeType.
Assembly.
GetType('System.Management.Automation.Configuration.PowerShellConfig')

$pscInstance = $pscType.
GetField('Instance', [Reflection.BindingFlags]'Static,NonPublic').
GetValue($null)

$getModulePathMethod = $pscType.GetMethod('GetModulePath', [Reflection.BindingFlags]'Instance,NonPublic')

if ($AllUsers) {
$getModulePathMethod.Invoke($pscInstance, $scopeType::AllUsers) ?? [Management.Automation.ModuleIntrinsics]::GetPSModulePath('BuiltIn')
} else {
$getModulePathMethod.Invoke($pscInstance, $scopeType::CurrentUser) ?? [Management.Automation.ModuleIntrinsics]::GetPSModulePath('User')
}
}

#endregion Helpers

### ISSUES
Expand Down
25 changes: 15 additions & 10 deletions ModuleFast.tests.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -562,19 +562,17 @@ Describe 'Install-ModuleFast' -Tag 'E2E' {

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

It 'Scope CurrentUser installs to $HOME\Documents\PowerShell\Modules' {
try {
Remove-Item $HOME\Documents\PowerShell\Modules\PrereleaseTest -Recurse -Force -ErrorAction SilentlyContinue
Install-ModuleFast @imfParams 'PrereleaseTest' -Scope CurrentUser
Install-ModuleFast @imfParams 'PrereleaseTest' -Destination CurrentUser
Resolve-Path $HOME\Documents\PowerShell\Modules\PrereleaseTest -EA Stop
} finally {
Remove-Item $HOME\Documents\PowerShell\Modules\PrereleaseTest -Recurse -Force -ErrorAction SilentlyContinue
Expand Down Expand Up @@ -764,7 +762,15 @@ Describe 'Install-ModuleFast' -Tag 'E2E' {

Describe 'GitHub Packages' {
It 'Gets Specific Module' {
if (-not (Get-Command Get-Secret -ea 0)) {
Set-ItResult -Skipped -Because 'SecretManagement Not Present'
return
}
$credential = [PSCredential]::new('Pester', (Get-Secret -Name 'ReadOnlyPackagesGithubPAT'))
if (-not $credential) {
Set-ItResult -Skipped -Because 'No ReadOnlyPackagesGithubPAT Credential'
return
}
$actual = Install-ModuleFast @imfParams -Specification 'PrereleaseTest=0.0.1' -Source 'https://nuget.pkg.github.com/justingrote/index.json' -Credential $credential -Plan
$actual.Name | Should -Be 'PrereleaseTest'
$actual.ModuleVersion | Should -Be '0.0.1'
Expand All @@ -778,4 +784,3 @@ Describe 'Install-ModuleFast' -Tag 'E2E' {
}
}
}

0 comments on commit 74bbac1

Please sign in to comment.