@@ -37,6 +37,7 @@ $SCRIPT:DefaultSource = 'https://pwsh.gallery/index.json'
3737
3838enum 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
0 commit comments