Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -2114,40 +2114,37 @@ protected override void ProcessRecord()
Path = new string[] { string.Empty };
}

foreach (string path in Path)
try
{
try
{
InvokeProvider.Item.New(path, Name, ItemType, Value, CmdletProviderContext);
}
catch (PSNotSupportedException notSupported)
{
WriteError(
new ErrorRecord(
notSupported.ErrorRecord,
notSupported));
}
catch (DriveNotFoundException driveNotFound)
{
WriteError(
new ErrorRecord(
driveNotFound.ErrorRecord,
driveNotFound));
}
catch (ProviderNotFoundException providerNotFound)
{
WriteError(
new ErrorRecord(
providerNotFound.ErrorRecord,
providerNotFound));
}
catch (ItemNotFoundException pathNotFound)
{
WriteError(
new ErrorRecord(
pathNotFound.ErrorRecord,
pathNotFound));
}
InvokeProvider.Item.New(Path, Name, ItemType, Value, CmdletProviderContext);
}
catch (PSNotSupportedException notSupported)
{
WriteError(
new ErrorRecord(
notSupported.ErrorRecord,
notSupported));
}
catch (DriveNotFoundException driveNotFound)
{
WriteError(
new ErrorRecord(
driveNotFound.ErrorRecord,
driveNotFound));
}
catch (ProviderNotFoundException providerNotFound)
{
WriteError(
new ErrorRecord(
providerNotFound.ErrorRecord,
providerNotFound));
}
catch (ItemNotFoundException pathNotFound)
{
WriteError(
new ErrorRecord(
pathNotFound.ErrorRecord,
pathNotFound));
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1128,7 +1128,7 @@ public Collection<PSObject> New(
/// If the provider threw an exception.
/// </exception>
internal void New(
string path,
string[] path,
string name,
string type,
object content,
Expand All @@ -1140,7 +1140,7 @@ internal void New(

// Parameter validation is done in the session state object

_sessionState.NewItem(new string[] { path }, name, type, content, context);
_sessionState.NewItem(path, name, type, content, context);
}

/// <summary>
Expand Down
97 changes: 33 additions & 64 deletions src/System.Management.Automation/engine/SessionStateContainer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3433,7 +3433,6 @@ internal void NewItem(
}

ProviderInfo provider = null;
PSDriveInfo driveInfo;
CmdletProvider providerInstance = null;

Collection<string> providerPaths = new Collection<string>();
Expand All @@ -3443,7 +3442,7 @@ internal void NewItem(
if (string.IsNullOrEmpty(name))
{
string providerPath =
Globber.GetProviderPath(resolvePath, context, out provider, out driveInfo);
Globber.GetProviderPath(resolvePath, context, out provider, out _);

providerInstance = GetProviderInstance(provider);
providerPaths.Add(providerPath);
Expand All @@ -3459,86 +3458,56 @@ internal void NewItem(
out providerInstance);
}

// Don't support 'New-Item -Type Directory' on the Function provider
// if the runspace has ever been in constrained language mode, as the mkdir
// function can be abused
if (context.ExecutionContext.HasRunspaceEverUsedConstrainedLanguageMode &&
Copy link
Member

Choose a reason for hiding this comment

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

You should add a test case to cover this

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

It is not new code. It is moved code.

(providerInstance is Microsoft.PowerShell.Commands.FunctionProvider) &&
(string.Equals(type, "Directory", StringComparison.OrdinalIgnoreCase)))
{
throw
PSTraceSource.NewNotSupportedException(SessionStateStrings.DriveCmdletProvider_NotSupported);
}

// Symbolic link targets are allowed to not exist on both Windows and Linux
bool allowNonexistingPath = false;
bool isSymbolicJunctionOrHardLink = false;

if (type != null)
{
WildcardPattern typeEvaluator = WildcardPattern.Get(type + "*", WildcardOptions.IgnoreCase | WildcardOptions.Compiled);
Copy link
Member

Choose a reason for hiding this comment

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

I don't understand the use of the wildcardpattern here, type is a string for filesystem that should be symboliclink, junction, hardlink, etc... right? why isn't this a switch statement?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

It is not new code. It is moved code.


allowNonexistingPath = typeEvaluator.IsMatch("symboliclink");

isSymbolicJunctionOrHardLink =
typeEvaluator.IsMatch("symboliclink")
|| typeEvaluator.IsMatch("junction")
|| typeEvaluator.IsMatch("hardlink");
}

foreach (string providerPath in providerPaths)
{
// Compose the globbed container and the name together to get a path
// to pass on to the provider.

string composedPath = providerPath;
if (!string.IsNullOrEmpty(name))
{
composedPath = MakePath(providerInstance, providerPath, name, context);
}

// Don't support 'New-Item -Type Directory' on the Function provider
// if the runspace has ever been in constrained language mode, as the mkdir
// function can be abused
if (context.ExecutionContext.HasRunspaceEverUsedConstrainedLanguageMode &&
(providerInstance is Microsoft.PowerShell.Commands.FunctionProvider) &&
(string.Equals(type, "Directory", StringComparison.OrdinalIgnoreCase)))
{
throw
PSTraceSource.NewNotSupportedException(SessionStateStrings.DriveCmdletProvider_NotSupported);
}

bool isSymbolicJunctionOrHardLink = false;
// Symbolic link targets are allowed to not exist on both Windows and Linux
bool allowNonexistingPath = false;

if (type != null)
{
WildcardPattern typeEvaluator = WildcardPattern.Get(type + "*", WildcardOptions.IgnoreCase | WildcardOptions.Compiled);

if (typeEvaluator.IsMatch("symboliclink") || typeEvaluator.IsMatch("junction") || typeEvaluator.IsMatch("hardlink"))
{
isSymbolicJunctionOrHardLink = true;
allowNonexistingPath = typeEvaluator.IsMatch("symboliclink");
}
}
string targetPath = content?.ToString();

if (isSymbolicJunctionOrHardLink)
{
string targetPath;

if (content is null || string.IsNullOrEmpty(targetPath = content.ToString()))
if (string.IsNullOrEmpty(targetPath))
{
throw PSTraceSource.NewArgumentNullException(nameof(content), SessionStateStrings.NewLinkTargetNotSpecified, path);
}

ProviderInfo targetProvider = null;
CmdletProvider targetProviderInstance = null;

var globbedTarget = Globber.GetGlobbedProviderPathsFromMonadPath(
targetPath,
allowNonexistingPath,
context,
out targetProvider,
out targetProviderInstance);

if (!string.Equals(targetProvider.Name, "filesystem", StringComparison.OrdinalIgnoreCase))
{
throw PSTraceSource.NewNotSupportedException(SessionStateStrings.MustBeFileSystemPath);
}

if (globbedTarget.Count > 1)
{
throw PSTraceSource.NewInvalidOperationException(SessionStateStrings.PathResolvedToMultiple, targetPath);
}

if (globbedTarget.Count == 0)
{
throw PSTraceSource.NewInvalidOperationException(SessionStateStrings.PathNotFound, targetPath);
}

// If the original target was a relative path, we want to leave it as relative if it did not require
// globbing to resolve.
if (WildcardPattern.ContainsWildcardCharacters(targetPath))
{
content = globbedTarget[0];
}
targetPath = targetPath?.Replace(StringLiterals.AlternatePathSeparator, StringLiterals.DefaultPathSeparator);
}

NewItemPrivate(providerInstance, composedPath, type, content, context);
NewItemPrivate(providerInstance, composedPath, type, targetPath, context);
}
}
}
Expand Down Expand Up @@ -3574,7 +3543,7 @@ private void NewItemPrivate(
CmdletProvider providerInstance,
string path,
string type,
object content,
string content,
CmdletProviderContext context)
{
// All parameters should have been validated by caller
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2380,8 +2380,6 @@ protected override void NewItem(
}

GetFileSystemInfo(normalizedTargetPath, out isDirectory);

strTargetPath = strTargetPath.Replace(StringLiterals.AlternatePathSeparator, StringLiterals.DefaultPathSeparator);
}
else
{
Expand Down
31 changes: 21 additions & 10 deletions src/System.Management.Automation/namespaces/LocationGlobber.cs
Original file line number Diff line number Diff line change
Expand Up @@ -757,20 +757,31 @@ internal Collection<string> GetGlobbedProviderPathsFromMonadPath(
}

PSDriveInfo drive = null;
string providerPath = GetProviderPath(path, context, out provider, out drive);

if (providerPath == null)
provider = null;
try
{
providerInstance = null;
s_tracer.WriteLine("provider returned a null path so return an empty array");
string providerPath = GetProviderPath(path, context, out provider, out drive);

s_pathResolutionTracer.WriteLine("Provider '{0}' returned null", provider);
return new Collection<string>();
}
if (providerPath == null)
{
providerInstance = null;
s_tracer.WriteLine("provider returned a null path so return an empty array");

if (drive != null)
s_pathResolutionTracer.WriteLine("Provider '{0}' returned null", provider);
return new Collection<string>();
}

if (drive != null)
{
context.Drive = drive;
}
}
catch (DriveNotFoundException)
{
context.Drive = drive;
if (!allowNonexistingPaths)
{
throw;
}
}

Collection<string> paths = new Collection<string>();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -277,22 +277,15 @@ Describe "New-Item with links" -Tags @('CI', 'RequireAdminOnWindows') {

Describe "New-Item: symlink with absolute/relative path test" -Tags @('CI', 'RequireAdminOnWindows') {
BeforeAll {
# on macOS, the /tmp directory is a symlink, so we'll resolve it here
$TestPath = $TestDrive
if ($IsMacOS)
{
$item = Get-Item $TestPath
$dirName = $item.BaseName
$item = Get-Item $item.PSParentPath -Force
if ($item.LinkType -eq "SymbolicLink")
{
$TestPath = Join-Path $item.Target $dirName
}
}

Push-Location $TestPath

$null = New-Item -Type Directory someDir
$null = New-Item -Type File someFile

$targets = "./$PID.tmp", ".\$PID.tmp"
$links = "$PID-Link0.tmp", "$PID-Link1.tmp"
}

AfterAll {
Expand All @@ -319,6 +312,51 @@ Describe "New-Item: symlink with absolute/relative path test" -Tags @('CI', 'Req
New-Item -Type SymbolicLink someFileLinkRelative -Target ./someFile
Get-Item someFileLinkRelative | Should -BeOfType System.IO.FileInfo
}

It "Can create symlink with relative nonexistent target '<target>'" -TestCases @{ link = $links[0]; target = $targets[0] }, @{ link = $links[1]; target = $targets[1] } {
param($link, $target)

$null = New-Item -Type SymbolicLink $link -Target $target

# Make sure the relative target path was recorded as such and uses
# the platform-appropriate separator.
(Get-Item $link).Target | Should -Be ($target -replace '[\\/]', [IO.Path]::DirectorySeparatorChar)
}

It "Can create symlink with absolute existent target as literal path" {
$fileName = 'file[1].txt'
$absolutePath = (New-Item -Type File $fileName).FullName

$link = New-Item -Type SymbolicLink globbedAbsoluteLink1 -Target $absolutePath
(Get-Item $link).Target | Should -BeExactly $absolutePath
}

It "Can create symlink with relative existent target as literal path" {
$fileName = 'file[1].txt'

if (-not $IsWindows) {
# Tracking issue https://github.com/PowerShell/PowerShell/issues/13365
Set-ItResult -Pending -Because "On Unix the Target property is always resolved to an absolute path."
}

$link = New-Item -Type SymbolicLink globbedRelativeLink1 -Target ./$fileName
(Get-Item $link).Target | Should -BeExactly ("./$fileName" -replace '[\\/]', [IO.Path]::DirectorySeparatorChar)
}

It "Can create symlink with absolute nonexistent target as literal path" {
$fileName = 'noexist_file[2].txt'
$absolutePath = Join-Path $pwd $fileName

$link = New-Item -Type SymbolicLink globbedAbsoluteLink2 -Target $absolutePath
(Get-Item $link).Target | Should -BeExactly $absolutePath
}

It "Can create symlink with relative nonexistent target as literal path" {
$fileName = 'noexist_file[2].txt'

$link = New-Item -Type SymbolicLink globbedRelativeLink2 -Target ./$fileName
(Get-Item $link).Target | Should -BeExactly ("./$fileName" -replace '[\\/]', [IO.Path]::DirectorySeparatorChar)
}
}

Describe "New-Item with links fails for non elevated user if developer mode not enabled on Windows." -Tags "CI" {
Expand Down