Skip to content

Commit 70750f1

Browse files
committed
Simplify ProfilePathInfo and ensure profile variable is set during startup
1 parent 28e1aa5 commit 70750f1

5 files changed

Lines changed: 77 additions & 52 deletions

File tree

src/PowerShellEditorServices.Hosting/Internal/EditorServicesRunner.cs

Lines changed: 6 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -267,18 +267,12 @@ private HostStartupInfo CreateHostStartupInfo()
267267
{
268268
_logger.Log(PsesLogLevel.Debug, "Creating startup info object");
269269

270-
ProfilePathInfo profilePaths = null;
271-
if (_config.ProfilePaths.AllUsersAllHosts != null
272-
|| _config.ProfilePaths.AllUsersCurrentHost != null
273-
|| _config.ProfilePaths.CurrentUserAllHosts != null
274-
|| _config.ProfilePaths.CurrentUserCurrentHost != null)
275-
{
276-
profilePaths = new ProfilePathInfo(
277-
_config.ProfilePaths.CurrentUserAllHosts,
278-
_config.ProfilePaths.CurrentUserCurrentHost,
279-
_config.ProfilePaths.AllUsersAllHosts,
280-
_config.ProfilePaths.AllUsersCurrentHost);
281-
}
270+
ProfilePathInfo profilePaths = new(
271+
_config.ProfilePaths.CurrentUserAllHosts,
272+
_config.ProfilePaths.CurrentUserCurrentHost,
273+
_config.ProfilePaths.AllUsersAllHosts,
274+
_config.ProfilePaths.AllUsersCurrentHost
275+
);
282276

283277
return new HostStartupInfo(
284278
_config.HostInfo.Name,

src/PowerShellEditorServices/Hosting/HostStartupInfo.cs

Lines changed: 8 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -190,32 +190,13 @@ public HostStartupInfo(
190190
}
191191

192192
/// <summary>
193-
/// This is a strange class that is generally <c>null</c> or otherwise just has a single path
194-
/// set. It is eventually parsed one-by-one when setting up the PowerShell runspace.
193+
/// Stores profile information passed from Start-EditorServices to be used for loading profiles if configured
194+
/// and for the $PROFILE variable in the initial session state.
195195
/// </summary>
196-
/// <remarks>
197-
/// TODO: Simplify this as a <see langword="record"/>.
198-
/// </remarks>
199-
public sealed class ProfilePathInfo
200-
{
201-
public ProfilePathInfo(
202-
string currentUserAllHosts,
203-
string currentUserCurrentHost,
204-
string allUsersAllHosts,
205-
string allUsersCurrentHost)
206-
{
207-
CurrentUserAllHosts = currentUserAllHosts;
208-
CurrentUserCurrentHost = currentUserCurrentHost;
209-
AllUsersAllHosts = allUsersAllHosts;
210-
AllUsersCurrentHost = allUsersCurrentHost;
211-
}
212-
213-
public string CurrentUserAllHosts { get; }
214-
215-
public string CurrentUserCurrentHost { get; }
216-
217-
public string AllUsersAllHosts { get; }
218-
219-
public string AllUsersCurrentHost { get; }
220-
}
196+
public readonly record struct ProfilePathInfo(
197+
string CurrentUserAllHosts,
198+
string CurrentUserCurrentHost,
199+
string AllUsersAllHosts,
200+
string AllUsersCurrentHost
201+
);
221202
}

src/PowerShellEditorServices/Services/PowerShell/Host/PsesInternalHost.cs

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -306,12 +306,20 @@ public async Task<bool> TryStartAsync(HostStartOptions startOptions, Cancellatio
306306
_logger.LogDebug("InitialWorkingDirectory set!");
307307
}
308308

309+
_logger.LogDebug("Setting profile variable...");
310+
await SetProfileVariableAsync(cancellationToken).ConfigureAwait(false);
311+
_logger.LogDebug("Profile variable set!");
312+
309313
if (startOptions.LoadProfiles)
310314
{
311315
_logger.LogDebug("Loading profiles...");
312316
await LoadHostProfilesAsync(cancellationToken).ConfigureAwait(false);
313317
_logger.LogDebug("Profiles loaded!");
314318
}
319+
else
320+
{
321+
_logger.LogDebug("Profile loading skipped per configuration!");
322+
}
315323

316324
if (!string.IsNullOrEmpty(startOptions.ShellIntegrationScript))
317325
{
@@ -583,13 +591,23 @@ internal void DisableTranscribeOnly()
583591
}
584592
}
585593

594+
internal Task SetProfileVariableAsync(CancellationToken cancellationToken)
595+
{
596+
// NOTE: This is a special task run on startup!
597+
return ExecuteDelegateAsync(
598+
"SetProfileVariable",
599+
executionOptions: null,
600+
(pwsh, _) => pwsh.SetProfileVariable(_hostInfo.ProfilePaths),
601+
cancellationToken);
602+
}
603+
586604
internal Task LoadHostProfilesAsync(CancellationToken cancellationToken)
587605
{
588606
// NOTE: This is a special task run on startup!
589607
return ExecuteDelegateAsync(
590608
"LoadProfiles",
591609
executionOptions: null,
592-
(pwsh, _) => pwsh.LoadProfiles(_hostInfo.ProfilePaths),
610+
(pwsh, _) => pwsh.LoadProfileScripts(_hostInfo.ProfilePaths),
593611
cancellationToken);
594612
}
595613

@@ -812,8 +830,9 @@ private void RunTopLevelExecutionLoop()
812830
{
813831
// Make sure we execute any startup tasks first. These should be, in order:
814832
// 1. Delegate to register psEditor variable
815-
// 2. LoadProfiles delegate
816-
// 3. Delegate to import PSEditModule
833+
// 2. SetProfileVariable delegate
834+
// 3. Optional LoadProfiles delegate
835+
// 4. Delegate to import PSEditModule
817836
while (_taskQueue.TryTake(out ISynchronousTask task))
818837
{
819838
task.ExecuteSynchronously(CancellationToken.None);

src/PowerShellEditorServices/Services/PowerShell/Utility/PowerShellExtensions.cs

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -193,7 +193,7 @@ public static void SetCorrectExecutionPolicy(this PowerShell pwsh, ILogger logge
193193
}
194194
}
195195

196-
public static void LoadProfiles(this PowerShell pwsh, ProfilePathInfo profilePaths)
196+
public static void SetProfileVariable(this PowerShell pwsh, ProfilePathInfo profilePaths)
197197
{
198198
// Per the documentation, "the `$PROFILE` variable stores the path to the 'Current User,
199199
// Current Host' profile. The other profiles are saved in note properties of the
@@ -202,15 +202,24 @@ public static void LoadProfiles(this PowerShell pwsh, ProfilePathInfo profilePat
202202
// https://docs.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_profiles?view=powershell-7.1#the-profile-variable
203203
PSObject profileVariable = PSObject.AsPSObject(profilePaths.CurrentUserCurrentHost);
204204

205+
profileVariable.Members.Add(new PSNoteProperty(nameof(profilePaths.AllUsersAllHosts), profilePaths.AllUsersAllHosts));
206+
profileVariable.Members.Add(new PSNoteProperty(nameof(profilePaths.AllUsersCurrentHost), profilePaths.AllUsersCurrentHost));
207+
profileVariable.Members.Add(new PSNoteProperty(nameof(profilePaths.CurrentUserAllHosts), profilePaths.CurrentUserAllHosts));
208+
profileVariable.Members.Add(new PSNoteProperty(nameof(profilePaths.CurrentUserCurrentHost), profilePaths.CurrentUserCurrentHost));
209+
210+
pwsh.Runspace.SessionStateProxy.SetVariable("PROFILE", profileVariable);
211+
}
212+
213+
public static void LoadProfileScripts(this PowerShell pwsh, ProfilePathInfo profilePaths)
214+
{
215+
PSObject profileVariable = PSObject.AsPSObject(profilePaths.CurrentUserCurrentHost);
216+
205217
PSCommand psCommand = new PSCommand()
206218
.AddProfileLoadIfExists(profileVariable, nameof(profilePaths.AllUsersAllHosts), profilePaths.AllUsersAllHosts)
207219
.AddProfileLoadIfExists(profileVariable, nameof(profilePaths.AllUsersCurrentHost), profilePaths.AllUsersCurrentHost)
208220
.AddProfileLoadIfExists(profileVariable, nameof(profilePaths.CurrentUserAllHosts), profilePaths.CurrentUserAllHosts)
209221
.AddProfileLoadIfExists(profileVariable, nameof(profilePaths.CurrentUserCurrentHost), profilePaths.CurrentUserCurrentHost);
210222

211-
// NOTE: This must be set before the profiles are loaded.
212-
pwsh.Runspace.SessionStateProxy.SetVariable("PROFILE", profileVariable);
213-
214223
// NOTE: Because it's possible there are no profiles defined, we might have an empty
215224
// command. Since this is being executed directly, we can't rely on `ThrowOnError =
216225
// false` to avoid an exception here. Instead, we must just not execute it.

test/PowerShellEditorServices.Test/Session/PsesInternalHostTests.cs

Lines changed: 28 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -105,23 +105,45 @@ public async Task CanCancelExecutionWithMethod()
105105
}
106106

107107
[Fact]
108-
public async Task CanHandleNoProfiles()
108+
public async Task CanHandleMissingProfilePaths()
109109
{
110-
// Call LoadProfiles with profile paths that won't exist, and assert that it does not
111-
// throw PSInvalidOperationException (which it previously did when it tried to invoke an
112-
// empty command).
110+
// Call LoadProfileScripts with profile paths that won't exist, and assert that it does
111+
// not throw PSInvalidOperationException (which it previously did when it tried to
112+
// invoke an empty command).
113113
ProfilePathInfo emptyProfilePaths = new("", "", "", "");
114114
await psesHost.ExecuteDelegateAsync(
115-
"LoadProfiles",
115+
"SetProfileVariableAndLoadProfileScripts",
116116
executionOptions: null,
117117
(pwsh, _) =>
118118
{
119-
pwsh.LoadProfiles(emptyProfilePaths);
119+
pwsh.SetProfileVariable(emptyProfilePaths);
120+
pwsh.LoadProfileScripts(emptyProfilePaths);
121+
122+
Assert.Equal(emptyProfilePaths.CurrentUserCurrentHost, pwsh.Runspace.SessionStateProxy.GetVariable("PROFILE"));
120123
Assert.Empty(pwsh.Commands.Commands);
121124
},
122125
CancellationToken.None);
123126
}
124127

128+
[Fact]
129+
public async Task SetsProfileVariableWhenProfilesAreNotLoaded()
130+
{
131+
// This host fixture starts with LoadProfiles = false. Ensure $PROFILE is still set.
132+
IReadOnlyList<string> profileVariable = await psesHost.ExecutePSCommandAsync<string>(
133+
new PSCommand().AddScript("$PROFILE"),
134+
CancellationToken.None);
135+
136+
Assert.Collection(profileVariable,
137+
(p) => Assert.Equal(PsesHostFactory.TestProfilePaths.CurrentUserCurrentHost, p));
138+
139+
// Ensure profile scripts were not loaded as part of startup.
140+
IReadOnlyList<PSObject> profileLoadedCommand = await psesHost.ExecutePSCommandAsync<PSObject>(
141+
new PSCommand().AddScript("Get-Command Assert-ProfileLoaded -ErrorAction Ignore"),
142+
CancellationToken.None);
143+
144+
Assert.Empty(profileLoadedCommand);
145+
}
146+
125147
// NOTE: Tests where we call functions that use PowerShell runspaces are slightly more
126148
// complicated than one would expect because we explicitly need the methods to run on the
127149
// pipeline thread, otherwise Windows complains about the the thread's apartment state not

0 commit comments

Comments
 (0)