Skip to content
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

InitialSessionState: Add support for configuring initial working directory #21446

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
143 changes: 83 additions & 60 deletions src/System.Management.Automation/engine/InitialSessionState.cs
Expand Up @@ -1678,6 +1678,7 @@ public InitialSessionState Clone()
ss.ThreadOptions = this.ThreadOptions;
ss.ThrowOnRunspaceOpenError = this.ThrowOnRunspaceOpenError;
ss.ApartmentState = this.ApartmentState;
ss.Location = this.Location;

foreach (ModuleSpecification modSpec in this.ModuleSpecificationsToImport)
{
Expand Down Expand Up @@ -1831,6 +1832,12 @@ public Microsoft.PowerShell.ExecutionPolicy ExecutionPolicy
/// </summary>
public bool ThrowOnRunspaceOpenError { get; set; } = false;

/// <summary>
/// If not null, the working location of the runspace is set to this path. If null,
/// the <see cref="Environment.CurrentDirectory">process working directory</see> is used as a default.
/// </summary>
public string Location { get; set; } = null;

/// <summary>
/// This property will be set only if we are refreshing the Type/Format settings by calling UpdateTypes/UpdateFormats directly.
/// In this case, we should wait until all type/format entries get processed. After that, if there were errors
Expand Down Expand Up @@ -2231,7 +2238,7 @@ internal void Bind(ExecutionContext context, bool updateOnly, PSModuleInfo modul
}
}

SetSessionStateDrive(context, setLocation: setLocation);
SetSessionStateDrive(context, setLocation);
}

private void Bind_SetVariables(SessionStateInternal ss)
Expand Down Expand Up @@ -3381,77 +3388,93 @@ internal void ResetRunspaceState(ExecutionContext context)
}
}

internal static void SetSessionStateDrive(ExecutionContext context, bool setLocation)
private static bool TryInitSessionStateCurrentDrive(ExecutionContext context)
{
// Set the starting location to the current process working directory
// Ignore any errors as the file system provider may not be loaded or
// a drive with the same name as the real file system drive may not have
// been mounted.
try
{
bool proceedWithSetLocation = true;
// Set the current drive to the first FileSystem drive if it exists.
ProviderInfo fsProvider = context.EngineSessionState.GetSingleProvider(context.ProviderNames.FileSystem);

if (context.EngineSessionState.ProviderCount > 0)
Collection<PSDriveInfo> fsDrives = fsProvider.Drives;
if (fsDrives != null && fsDrives.Count > 0)
{
// NTRAID#Windows Out Of Band Releases-908481-2005/07/01-JeffJon
// Make sure we have a CurrentDrive set so that we can deal with
// UNC paths
context.EngineSessionState.CurrentDrive = fsDrives[0];
return true;
}
}
catch (ProviderNotFoundException)
{
}

if (context.EngineSessionState.CurrentDrive == null)
{
bool fsDriveSet = false;
try
{
// Set the current drive to the first FileSystem drive if it exists.
ProviderInfo fsProvider = context.EngineSessionState.GetSingleProvider(context.ProviderNames.FileSystem);
Collection<PSDriveInfo> allDrives = context.EngineSessionState.Drives(null);

Collection<PSDriveInfo> fsDrives = fsProvider.Drives;
if (fsDrives != null && fsDrives.Count > 0)
{
context.EngineSessionState.CurrentDrive = fsDrives[0];
fsDriveSet = true;
}
}
catch (ProviderNotFoundException)
{
}
if (allDrives != null && allDrives.Count > 0)
{
context.EngineSessionState.CurrentDrive = allDrives[0];
return true;
}
else
{
return false;
}
}

if (!fsDriveSet)
{
Collection<PSDriveInfo> allDrives = context.EngineSessionState.Drives(null);
private void SetSessionStateDrive(ExecutionContext context, bool setLocation)
{
if (context.EngineSessionState.ProviderCount == 0)
{
// If there are no defined providers, but a custom location was set, the location cannot exist, throw an error.
if (setLocation && Location != null)
{
throw new ItemNotFoundException(Location, "PathNotFound", SessionStateStrings.PathNotFound);
}

if (allDrives != null && allDrives.Count > 0)
{
context.EngineSessionState.CurrentDrive = allDrives[0];
}
else
{
ItemNotFoundException itemNotFound =
new ItemNotFoundException(Directory.GetCurrentDirectory(), "PathNotFound", SessionStateStrings.PathNotFound);
return;
}

context.ReportEngineStartupError(itemNotFound);
proceedWithSetLocation = false;
}
}
}
// NTRAID#Windows Out Of Band Releases-908481-2005/07/01-JeffJon
// Make sure we have a CurrentDrive set so that we can deal with
// UNC paths
if (context.EngineSessionState.CurrentDrive == null && !TryInitSessionStateCurrentDrive(context))
{
// FIXME: this is a wrong exception to throw, we don't yet know that the path was not found
ItemNotFoundException itemNotFound = new(Location ?? Environment.CurrentDirectory, "PathNotFound", SessionStateStrings.PathNotFound);
context.ReportEngineStartupError(itemNotFound);
return;
}

if (proceedWithSetLocation && setLocation)
{
CmdletProviderContext providerContext = new CmdletProviderContext(context);
if (!setLocation)
{
return;
}

try
{
providerContext.SuppressWildcardExpansion = true;
context.EngineSessionState.SetLocation(Directory.GetCurrentDirectory(), providerContext);
}
catch (ItemNotFoundException)
{
// If we can't access the Environment.CurrentDirectory, we may be in an AppContainer. Set the
// default drive to $pshome
string defaultPath = System.IO.Path.GetDirectoryName(Environment.ProcessPath);
context.EngineSessionState.SetLocation(defaultPath, providerContext);
}
}
var providerContext = new CmdletProviderContext(context) { SuppressWildcardExpansion = true };

// User set a custom initial working location.
if (Location != null)
{
// If the location is invalid or does not exist, let the exception bubble up; since the user explicitly
// configured the working directory, he probably wants to get notified on failure.
context.EngineSessionState.SetLocation(Location, providerContext);
return;
}

// As a fallback, set the starting location to the current process working directory.
// Ignore any errors as the file system provider may not be loaded or
// a drive with the same name as the real file system drive may not have
// been mounted.
try
{
try
{
context.EngineSessionState.SetLocation(Environment.CurrentDirectory, providerContext);
}
catch (ItemNotFoundException)
{
// If we can't access the Environment.CurrentDirectory, we may be in an AppContainer. Set the
// default drive to $pshome
string defaultPath = Path.GetDirectoryName(Environment.ProcessPath);
context.EngineSessionState.SetLocation(defaultPath, providerContext);
}
}
catch (Exception)
Expand Down
66 changes: 66 additions & 0 deletions test/xUnit/csharp/test_InitialSessionState.cs
@@ -0,0 +1,66 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

using System;
using System.IO;
using System.Linq;
using System.Management.Automation;
using System.Management.Automation.Runspaces;
using Xunit;
using DriveNotFoundException = System.Management.Automation.DriveNotFoundException;

namespace PSTests.Parallel;

public class InitialSessionStateTests
{
[Fact]
public void TestDefaultLocation()
{
var wd = GetResultingLocation(null);
Assert.Equal(Environment.CurrentDirectory, wd.Path);
}

[Fact]
public void TestCustomFileSystemLocation()
{
// any path different from the process working directory would work here
var location = Path.GetTempPath().TrimEnd(Path.DirectorySeparatorChar);

// sanity check to ensure the test is not running from the tmp dir
Assert.NotEqual(location, Environment.CurrentDirectory);

var wd = GetResultingLocation(location);
Assert.Equal(location, wd.Path);
}

[Fact]
public void TestCustomEnvLocation()
{
var wd = GetResultingLocation("Env:");
Assert.Equal("Env:" + Path.DirectorySeparatorChar, wd.Path);
}

[Fact]
public void TestCustomLocation_NonExistentDrive()
{
Assert.Throws<DriveNotFoundException>(() => { GetResultingLocation("NonExistentDrive:"); });
}

[Fact]
public void TestCustomLocation_NonExistentPath()
{
Assert.Throws<ItemNotFoundException>(() => { GetResultingLocation("Temp:/nonexistent test directory"); });
}

private static PathInfo GetResultingLocation(string issLocation)
{
var iss = InitialSessionState.CreateDefault2();
iss.Location = issLocation;

using var runspace = RunspaceFactory.CreateRunspace(iss);
runspace.Open();

using var ps = PowerShell.Create(runspace);
return ps.AddCommand("pwd").Invoke<PathInfo>().Single();
}
}