When people build non-module tools that use the PowerShell SDK, they ultimately need to invoke some PowerShell commands. For tools are meant to function as a black box, the PowerShell commands that the tool invokes...
$PSDefaultParameterValues;$*Preference variables;This needs to be very, very easy so that tools can work as they were designed, without changes in behavior because of user preferences, and without the PowerShell debugger tripping on non-user code that is meant to be internal.
For a related discussion that highlights what tool makers need to think about in detail, see this comment in issue #2121 of PowerShell\vscode-powershell. That discussion also shows the scaffolding that is required to do something that should be very simple and supported in the SDK via a few flags.
Any public interface that allows users to build PowerShell commands piecemeal (i.e. AddCommand, AddParameter, AddScript, AddArgument, etc.) and then invoke them needs to support invocation options that:
$PSDefaultParameterValues;~That's not very specific, so obviously more investigation would be necessary to identify where to create these options so that they can be used via public interfaces, and where the options would need to be plumbed through the PowerShell runtime so that toolmakers get the results they expect.~
_Update copied from discussion below_:
To achieve this goal, S.M.A.PSInvocationSettings will be extended with a DebuggerHidden property, which would ensure the commands were hidden from the debugger, and a IgnorePreferenceVariables property, which would ignore $PSDefaultParameterValues and $*Preference variables. Programmers using the SDK can pass their PSInvocationSettings instance into S.M.A.PowerShell.Invoke, and with the new settings properly plumbed through the runtime, they won't have to worry about user preferences impacting internal APIs.
@KirkMunro Why not just use a separate runspace.
@BrucePay That's a great question, and I'm going to think about that for a bit.
Now you've got me wondering if this is as simple as something that just isn't obvious because it's not a documented best practice to use for things like this, but maybe it should be.
As someone who has worked on PowerShell tools for many years, where this problem has shown up here and there, I wonder why I never thought of doing that. I'm not the only one, though, because this showed up in PowerGUI, it was an issue in PowerWF/PowerSE, and now it shows up in VSCode-PowerShell, and nobody involved other than you, just now, suggested using a separate runspace, so it's not an obvious design approach that just jumps out at you. At least, not until you hear it from @BrucePay.
@BrucePay After thinking on this some more, doubting that it's really that simple, and seeing @SeeminglyScience confirm that it's not that simple, I'm back to my original thought: it's not that simple, and the SDK should support working with PowerShell in a runspace without taking user preferences into account.
Tools that don't care about state could use their own runspace, but tools that are built on top of PowerShell, offering an IDE, or debugging, etc. need state information from the user's runspace to ensure that they are working with the same information that is available to the user, and they wouldn't have access to that information if they used a separate runspace.
@KirkMunro
need state information from the user's runspace
Conveniently available through the SessionStateProxy APIs on the runspace :-)
need state information from the user's runspace
ISE seemed to do OK. It managed, manipulated state and debugged multiple runspaces both local and remote.
Anyway, this _might_ help (assuming it's still there). As you know, a runspace is all about state, specifically session state. There is one global session state object per runspace and then one each for each module loaded. The module session state objects are linked to the global session state object so they all share the global namespace. The global session state object is linked to null. However, we designed things so that it's possible for a non-global session state object to be created that isn't linked to anything. We figured that there might be scenarios where you wanted an isolated session state even though we didn't know of any such scenarios. So you might be able to create an isolated "tools" environment using this mechanism (assuming it still exists and is public - I haven't looked.) But note - this is an empty session state - no commands, no variables, nothing. You'd have to manually populate it to have any commands available to you.
Is that the right way to go for this though?
Creating/managing a session state for this seems more complicated than what I was thinking.
I was leaning more towards adding properties to S.M.A.PSInvocationSettings, such as DebuggerHidden, which would ensure the commands were hidden from the debugger, and IgnorePreferenceVariables, which would ignore $PSDefaultParameterValues and $*Preference variables. Then programmers using the SDK could pass their PSInvocationSettings instance into S.M.A.PowerShell.Invoke, and as long as the new settings were properly plumbed through the runtime, they wouldn't have to worry about user preferences impacting internal APIs.
ISE seemed to do OK. It managed, manipulated state and debugged multiple runspaces both local and remote.
ISE isn't that advanced as far as IDEs go, and it is still susceptible to internal behavior that can be unintentionally manipulated by use of $PSDefaultParameterValues, breakpoints, and $*Preference variables. PowerShell itself suffers the same problem. It just doesn't show up that often, but regardless of the frequency, SDK users should be able to use the runtime in a way that internals are kept internal.
ISE seemed to do OK. It managed, manipulated state and debugged multiple runspaces both local and remote.
ISE isn't that advanced as far as IDEs go
Yeah the ISE could get away with a bit more because it doesn't try to do a whole lot. It also had the benefit of InternalsVisibleTo. I can't look at the code to check obviously, but in the few places that it does go the extra mile, I'm guessing it leans on internals heavily.