Skip to content

Move PS content out of OneDrive #388

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 11 commits into
base: master
Choose a base branch
from
133 changes: 133 additions & 0 deletions Draft-Accepted/RFC0066-PowerShell-User-Content-Location.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
---
RFC: RFC0066
Author: Justin Chung
Status: Draft
SupercededBy: N/A
Version: 1.0
Area: Core
Comments Due: 07/31/2025
Plan to implement: Yes
---

# PowerShell User Content Location

This RFC proposes moving the current PowerShell user content location out of OneDrive &
default to the `AppData` directory on Windows machines.

## Motivation

```
As a user,
I can customize the location where PowerShell user content is installed,
so that I can avoid problems created by file sync solutions like OneDrive.

As an Admin,
I must be able to customize the location set for my users either pre/during/post install,
so that I can better manage my environment and better secure and support my users,
to reduce unnecessary support tickets to my IT Team.
```

- PowerShell currently places profile, modules, and configuration files in the user's Documents
folder, which is against established conventions for shell configurations and tools.
- PowerShell content files in OneDrive can lead to unwanted syncing of module files, leading to
various issues.
- There is strong community demand for changing this behavior as the current setup is problematic
for many users.
- Changing the default location would align PowerShell with other developer tools and improve
usability.
Comment on lines +30 to +37

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just so that this is commented as part of this RFC, I'm in the so what camp, especially as PowerShell isn't like other similar tools, so we don't have to stick to those conventions, but also fully understand why we need to do this.

The benefit in keeping as is, is for use with roaming profiles which on *nix isn't a thing in the same way as it is on Windows, but is by users and organisations & especially those not using Cloud Tech and still heavy in on prem installs of AD & other software.

It also goes against giving the user/admin choices, but I do agree we should have a better default, that whilst I don't really think needs to map to other tools, it helps adoption and appeases those coming to PowerShell from those other tools, even if in my opinion there's lots more benefit to using the current set up and allowing the sync with tools like OneDrive, which is something I've used & blogged about using since 2016, as opposed to other tools like ChezMoi (if that's the right spelling)


## Specification

- This will be an experimental feature.
- The content folder location change will only apply to PowerShell on Windows.
- Configurability of the content folder will apply to all platforms.
- A configuration file in the PowerShell user content folder will determine the location of the user
scoped **PSModulePath**.
- By default, the PowerShell user content folder will be located in the
`$env:LOCALAPPDATA\PowerShell`.
- The new location becomes the location used as the `CurrentUser` scope for PSResourceGet.
- The proposed directory structure:

```
C:\Users\UserName\AppData\Local\PowerShell\
├── powershell.config.json (Not Configurable)
├── Scripts (Configurable)
├── Modules (Configurable)
├── Help (Configurable)
└── <*profile>.ps1 (Configurable)
```

- The following setting is added to the `powershell.config.json` file:

**UserPSContentPath** specifies the full path of the content folder. The default value is
`$env:LOCALAPPDATA\PowerShell`. The user can change this value to a different path.

```json
{
"UserPSContentPath" : "$env:LOCALAPPDATA\\PowerShell",
}
```

## User Experience

- On startup PowerShell will create a directory in AppData and a configuration file.
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will it be created empty or with UserPSContentPath predefined?

This is essential for the machine config discussed below unless machine-level gets precedence (ideally). User-level precedence and potentially predefined setting in user config would block any machine config.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm thinking with the experimental feature turned on UserPSContentPath will be predefined in the config file pointing to the new "recommended" location in LocalAppData. But all of the directories will be empty and the user will have to migrate on their own. As of now for this migration we are thinking about providing an example script.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds good, as long as the machine config setting with variable support takes precedence. 🙂

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should also really add this as part of the install options (again after this has been implemented) as to reduce the need for startup to check and create them

Suggested change
- On startup PowerShell will create a directory in AppData and a configuration file.
- On startup PowerShell will create a directory in AppData and a configuration file if they don't exist.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure I follow. It will always have to test the path to cover deleted/renamed config and new user profiles.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we create the MyDocument\PowerShell path up on startup today? If so, let's create the env:LocalAppData\powershell folder when starting up; otherwise, let's keep the current behavior.

As for the configuration file, I think there is no need to create it up on start. If the file is missing, we just use the default content location env:LocalAppData\powershell.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The current behavior is to just point to the location, Install-Module is the one that creates the directory there.
OK will update.

- The user scoped **PSModulePath** will point to `Modules` folder under the location specified by
**UserPSContentPath**.
- Users can configure a custom location for PowerShell user content by changing the value of
**UserPSContentPath**.
- Users will initially need to manually move/copy their existing PowerShell user content
from the Documents folder to the new location after enabling the feature.
- We intend to add a new cmdlet to aid this process in a future release shortly
after this work has been completed as part of the work into hopefully the 7.6.0 GA
release or another 7.6.x release. Details on this to follow.

## Other considerations

- The following functionalities will be affected:
- SecretManagement
- SecretManagement extension vaults are registered for the current user context in:
`$env:LOCALAPPDATA\Microsoft\PowerShell\secretmanagement\secretvaultregistry\`

When an extension vault is registered, SecretManagement stores the full path to the extension
module in the registry. Moving the PowerShell content to a new location will break the vault
registrations.
- Document instructions on how to re-register vaults after moving the content folder.
- Document the need to keep Modules in the Documents folder to so that SecretManagement
continues to work for multiple installs of PowerShell 7 (stable and preview).
Comment on lines +95 to +96
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this technically possible? There is only one copy of the vault registration, so I guess only 1 registered location of a vault module.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a SOW for Secrets Management & should include a Migration cmdlet as part of that feature, which would align to the document comments.

The last comment can be managed without the use of the docs folder but in line with my other comments about the benefits of using that documents folder already & by having a configurable location you can enable seperate Secret Management settings per version (allowing different operations for different personas like for MSP's etc)


- Use the following script to copy the PowerShell contents folder:

```pwsh
$newPath = "C:\Custom\PowerShell\Modules"
$currentUserModulePath = [System.Environment]::GetFolderPath('MyDocuments') + "\PowerShell"
Copy-Item -Path $currentUserModulePath -Destination $newPath -Recurse -Force
```
Comment on lines +100 to +104
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you may want to have a script to show users what vault modules are registered today and their hardcoded paths in the registration.


- PowerShellGet is hardcoded to install scripts and modules in the user's `Documents` folder. It
will not support this feature.

- The following are required changes to PowerShell due to the content folder change:
- Profile path will need to use the API to get the content folder path.
- Updateable help path needs to use the API to get the content folder path.
- Scripts path will need to use the API to get the content folder path.
- Module path will ned to use the API to get the content folder path.

## Implementation questions
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you please lay out all the changes that are needed to PowerShell corresponding to the content folder change? For example, I presume the updateable help needs to be changed to honor the new help content location, but I don't see it mentioned in the RFC.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So far I see profile, help, scripts, and module paths need to use the new API that looks at the config.json or returns the default location LOCALAPPDATA.


- Will the experimental feature be enabled by default?
- No, the user should explicitly enable the feature and copy their existing
PowerShell user content to the new location.
- This feature is planned to be available on preview versions first then stable and LTS versions
later.

- How does `$PROFILE` get populated?
- Can profile scripts be moved to `PSContent`?
- The feature needs to update `$PROFILE` to point to profile scripts in the new location.

- What happens if **UserPSContentPath** is added to the machine-level configuration file in
`$PSHOME/powershell.config.json`?
- Ignore the setting in the machine-level configuration file since this is a user
setting. No error - just ignore it.

- Will **UserPSContentPath** support environment variables (like `$env:USERNAME` or `%USERNAME%`)?
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is required to support the machine-wide config at all without mixing user content, isn't it? env:UserName, $HOME/$env:USERPROFILE, $env:LocalAppData etc.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think yes the goal is to support environment variables. I think this would be very useful.
But for machine-wide config I think we are not touching those that are in program files at this time. We are only trying to move the user PSContent folder out of myDocuments

Copy link
Member

@daxian-dbw daxian-dbw Jul 17, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I hope you are not planning to evaluate the string value as PowerShell script, as that would certainly raise security concerns. Also, that implies a change in the order of processing -- the Runspace needs to be ready before we can handle this key.

I agree it should support environment variables, but we need to parse and replace the env variables like pre-defined variables in ADO YAML (or like in the VSCode mcp setting file), instead of depending on evaluating the whole string value as PowerShell script.

Maybe the syntax for env variables should be like $(env:var-name) (not a good example maybe, still looks quite like PS script :(), to make it a bit clearer to the user that the value is not a PowerShell expression that will be evaluated.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should not do what ADO YAML does or reinvent a new syntax like it but we need a suitable cross plat one that is recommended. Perhaps using the ENV:\ PSDrive path could be an option - thoughts on that @daxian-dbw ?

if the PowerShell.Config was a psd1 not json you'd get rid of some of the many headaches that json & Yaml bring us.

We also really should build these files and pre-populate the full path, not do a potentially dangerous lookup operation.

Seperate note - Machine level config should also really move to ProgramData on Windows out of ProgramFiles but is out scope for this RFC

Copy link

@fflaten fflaten Jul 20, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PowerShell syntax was just a OS-neutral example from my side. My expectation was access to at least some OS system/user/process variables in a cross-platform compatible format, not PowerShell expressions. 👍

Perhaps using the ENV:\ PSDrive path could be an option

IMO this would add to the confusion that it may be resolved in a runspace.

We also really should build these files and pre-populate the full path, not do a potentially dangerous lookup operation

In a ideal world, yes. Docs can have a security recommendation to use static/expanded paths. For user config it could potentially be the only option.

We still need variables for machine-config to have a single value/template that can reference the username and user profile location. The alternatives would be deployment or logon scripts to modify user config for everyone (incl new user profiles) which adds complexity and potentially cost, delays and risk for the user/company.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@daxian-dbw I was thinking of only supporting a few known variables, not user defined ones. We maybe add to this list in the future?

- This could enable a global configuration scenario if we allowed configuration in `$PSHOME`.