Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
109 changes: 75 additions & 34 deletions src/System.Management.Automation/namespaces/FileSystemProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1311,35 +1311,34 @@ protected override void GetItem(string path)
// If we want to retrieve the file streams, retrieve them.
if (retrieveStreams)
{
if (!isContainer)
foreach (string desiredStream in dynamicParameters.Stream)
{
foreach (string desiredStream in dynamicParameters.Stream)
// See that it matches the name specified
WildcardPattern p = WildcardPattern.Get(desiredStream, WildcardOptions.IgnoreCase | WildcardOptions.CultureInvariant);
bool foundStream = false;

foreach (AlternateStreamData stream in AlternateDataStreamUtilities.GetStreams(result.FullName))
{
// See that it matches the name specified
WildcardPattern p = WildcardPattern.Get(desiredStream, WildcardOptions.IgnoreCase | WildcardOptions.CultureInvariant);
bool foundStream = false;
if (!p.IsMatch(stream.Stream))
{ continue; }

foreach (AlternateStreamData stream in AlternateDataStreamUtilities.GetStreams(result.FullName))
{
if (!p.IsMatch(stream.Stream)) { continue; }
string outputPath = result.FullName + ":" + stream.Stream;
// Alternate data streams can never be containers.
WriteItemObject(stream, outputPath, false);
foundStream = true;
}

string outputPath = result.FullName + ":" + stream.Stream;
WriteItemObject(stream, outputPath, isContainer);
foundStream = true;
}
if ((!WildcardPattern.ContainsWildcardCharacters(desiredStream)) && (!foundStream))
{
string errorMessage = StringUtil.Format(
FileSystemProviderStrings.AlternateDataStreamNotFound, desiredStream, result.FullName);
Exception e = new FileNotFoundException(errorMessage, result.FullName);

if ((!WildcardPattern.ContainsWildcardCharacters(desiredStream)) && (!foundStream))
{
string errorMessage = StringUtil.Format(
FileSystemProviderStrings.AlternateDataStreamNotFound, desiredStream, result.FullName);
Exception e = new FileNotFoundException(errorMessage, result.FullName);

WriteError(new ErrorRecord(
e,
"AlternateDataStreamNotFound",
ErrorCategory.ObjectNotFound,
path));
}
WriteError(new ErrorRecord(
e,
"AlternateDataStreamNotFound",
ErrorCategory.ObjectNotFound,
path));
}
}
}
Expand Down Expand Up @@ -6666,7 +6665,14 @@ public IContentReader GetContentReader(string path)

try
{
if (Directory.Exists(path))
// Get-Content will write a non-terminating error if the target is a directory.
// On Windows, the streamName must be null or empty for it to write the error. Otherwise, the
// alternate data stream is not a directory, even if it's set on a directory.
if (Directory.Exists(path)
#if !UNIX
&& string.IsNullOrEmpty(streamName)
#endif
)
{
string errMsg = StringUtil.Format(SessionStateStrings.GetContainerContentException, path);
ErrorRecord error = new ErrorRecord(new InvalidOperationException(errMsg), "GetContainerContentException", ErrorCategory.InvalidOperation, null);
Expand Down Expand Up @@ -6821,7 +6827,14 @@ public IContentWriter GetContentWriter(string path)

try
{
if (Directory.Exists(path))
// Add-Content and Set-Content will write a non-terminating error if the target is a directory.
// On Windows, the streamName must be null or empty for it to write the error. Otherwise, the
// alternate data stream is not a directory, even if it's set on a directory.
if (Directory.Exists(path)
#if !UNIX
&& string.IsNullOrEmpty(streamName)
#endif
)
{
string errMsg = StringUtil.Format(SessionStateStrings.WriteContainerContentException, path);
ErrorRecord error = new ErrorRecord(new InvalidOperationException(errMsg), "WriteContainerContentException", ErrorCategory.InvalidOperation, null);
Expand Down Expand Up @@ -6899,13 +6912,6 @@ public void ClearContent(string path)

path = NormalizePath(path);

if (Directory.Exists(path))
{
string errorMsg = StringUtil.Format(SessionStateStrings.ClearDirectoryContent, path);
WriteError(new ErrorRecord(new NotSupportedException(errorMsg), "ClearDirectoryContent", ErrorCategory.InvalidOperation, path));
return;
}

try
{
#if !UNIX
Expand Down Expand Up @@ -6957,6 +6963,26 @@ public void ClearContent(string path)
clearStream = false;
}

#endif
// On Windows, determine if our argument is a directory only after we determine if
// we're being asked to work with an alternate data stream, because directories can have
// alternate data streams on them that are not child items. These alternate data streams
// must be treated as data streams, even if they're attached to directories. However,
// if asked to work with a directory without a data stream specified, write a non-terminating
// error instead of clearing all child items of the directory. (On non-Windows, alternate
// data streams don't exist, so in that environment always write the error when addressing
// a directory.)
if (Directory.Exists(path)
#if !UNIX
&& !clearStream
#endif
)
{
string errorMsg = StringUtil.Format(SessionStateStrings.ClearDirectoryContent, path);
WriteError(new ErrorRecord(new NotSupportedException(errorMsg), "ClearDirectoryContent", ErrorCategory.InvalidOperation, path));
return;
}
#if !UNIX
if (clearStream)
{
FileStream fileStream = null;
Expand Down Expand Up @@ -8622,7 +8648,21 @@ internal static List<AlternateStreamData> GetStreams(string path)
SafeFindHandle handle = NativeMethods.FindFirstStreamW(
path, NativeMethods.StreamInfoLevels.FindStreamInfoStandard,
findStreamData, 0);
if (handle.IsInvalid) throw new Win32Exception();
if (handle.IsInvalid)
{
int error = Marshal.GetLastWin32Error();

// Directories don't normally have alternate streams, so this is not an exceptional state.
// If a directory has no alternate data streams, FindFirstStreamW returns ERROR_HANDLE_EOF.
if (error == NativeMethods.ERROR_HANDLE_EOF)
{
return alternateStreams;
}

// An unexpected error was returned, that we don't know how to interpret. The most helpful
// thing we can do at this point is simply throw the raw Win32 exception.
throw new Win32Exception(error);
}

try
{
Expand Down Expand Up @@ -8757,6 +8797,7 @@ internal static void SetZoneOfOrigin(string path, SecurityZone securityZone)
internal static class NativeMethods
{
internal const int ERROR_HANDLE_EOF = 38;
internal const int ERROR_INVALID_PARAMETER = 87;

internal enum StreamInfoLevels { FindStreamInfoStandard = 0 }

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ Describe "Add-Content cmdlet tests" -Tags "CI" {
BeforeAll {
$file1 = "file1.txt"
Setup -File "$file1"
$streamContent = "ShouldWork"
}

Context "Add-Content should actually add content" {
Expand Down Expand Up @@ -47,6 +48,27 @@ Describe "Add-Content cmdlet tests" -Tags "CI" {
{ Add-Content -Path . -Value "WriteContainerContentException" -ErrorAction Stop } | Should -Throw -ErrorId "WriteContainerContentException,Microsoft.PowerShell.Commands.AddContentCommand"
}

Context "Add-Content should work with alternate data streams on Windows" {
BeforeAll {
if (!$isWindows) {
return
}
$ADSTestDir = "addcontentadstest"
$ADSTestFile = "addcontentads.txt"
$streamContent = "This is a test stream."
Setup -Directory "$ADSTestDir"
Setup -File "$ADSTestFile"
}
It "Should add an alternate data stream on a directory" -Skip:(!$IsWindows) {
Add-Content -Path TestDrive:\$ADSTestDir -Stream Add-Content-Test-Stream -Value $streamContent -ErrorAction Stop
Get-Content -Path TestDrive:\$ADSTestDir -Stream Add-Content-Test-Stream | Should -BeExactly $streamContent
}
It "Should add an alternate data stream on a file" -Skip:(!$IsWindows) {
Add-Content -Path TestDrive:\$ADSTestFile -Stream Add-Content-Test-Stream -Value $streamContent -ErrorAction Stop
Get-Content -Path TestDrive:\$ADSTestFile -Stream Add-Content-Test-Stream | Should -BeExactly $streamContent
}
}

#[BugId(BugDatabase.WindowsOutOfBandReleases, 906022)]
It "should throw 'NotSupportedException' when you add-content to an unsupported provider" -Skip:($IsLinux -Or $IsMacOS) {
{ Add-Content -Path HKLM:\\software\\microsoft -Value "ShouldNotWorkBecausePathIsUnsupported" -ErrorAction Stop } | Should -Throw -ErrorId "NotSupported,Microsoft.PowerShell.Commands.AddContentCommand"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@ Describe "Clear-Content cmdlet tests" -Tags "CI" {
Setup -File "$file3" -Content $content2
$streamContent = "content for alternate stream"
$streamName = "altStream1"
$dirName = "clearcontent"
Setup -Directory "$dirName"
}

Context "Clear-Content should actually clear content" {
Expand All @@ -75,32 +77,50 @@ Describe "Clear-Content cmdlet tests" -Tags "CI" {
$cci.SupportsShouldProcess | Should -BeTrue
}

It "Alternate streams should be cleared with clear-content" -Skip:(!$IsWindows) {
# make sure that the content is correct
# this is here rather than BeforeAll because only windows can write to an alternate stream
Set-Content -Path "TestDrive:/$file3" -Stream $streamName -Value $streamContent
Get-Content -Path "TestDrive:/$file3" | Should -BeExactly $content2
Get-Content -Path "TestDrive:/$file3" -Stream $streamName | Should -BeExactly $streamContent
Clear-Content -Path "TestDrive:/$file3" -Stream $streamName
Get-Content -Path "TestDrive:/$file3" | Should -BeExactly $content2
Get-Content -Path "TestDrive:/$file3" -Stream $streamName | Should -BeNullOrEmpty
}
Context "Clear-Content should work with alternate data streams on Windows" {
It "Alternate streams should be cleared with Clear-Content on a file" -Skip:(!$IsWindows) {

Set-Content -Path "TestDrive:/$file3" -Stream $streamName -Value $streamContent
Get-Content -Path "TestDrive:/$file3" -Stream $streamName | Should -BeExactly $streamContent

Clear-Content -Path "TestDrive:/$file3" -Stream $streamName -ErrorAction Stop

$result = Get-Item -Path "TestDrive:/$file3" -Stream $streamName
$result | Should -BeOfType System.Management.Automation.Internal.AlternateStreamData
$result.length | Should -Be 0
}

It "the '-Stream' dynamic parameter is visible to get-command in the filesystem" -Skip:(!$IsWindows) {
try {
Push-Location -Path TestDrive:
(Get-Command Clear-Content -Stream foo).parameters.keys -eq "stream" | Should -Be "stream"
It "Alternate streams should be cleared with Clear-Content on a directory" -Skip:(!$IsWindows) {
Set-Content -Path "TestDrive:/$dirName" -Stream $streamName -Value $streamContent

Get-Content -Path "TestDrive:/$dirName" -Stream $streamName | Should -BeExactly $streamContent
Clear-Content -Path "TestDrive:/$dirName" -Stream $streamName -ErrorAction Stop

$result = Get-Item -Path "TestDrive:/$dirName" -Stream $streamName
$result | Should -BeOfType System.Management.Automation.Internal.AlternateStreamData
$result.length | Should -Be 0
}
finally {
Pop-Location

It "the '-Stream' dynamic parameter is visible to get-command in the filesystem" -Skip:(!$IsWindows) {
try {
Push-Location -Path TestDrive:
(Get-Command Clear-Content -Stream foo).parameters.keys -eq "Stream" | Should -BeExactly "Stream"
}
finally {
Pop-Location
}
}
}

It "the '-Stream' dynamic parameter should not be visible to get-command in the function provider" {
Push-Location -Path function:
{ Get-Command Clear-Content -Stream $streamName } |
Should -Throw -ErrorId "NamedParameterNotFound,Microsoft.PowerShell.Commands.GetCommandCommand"
Pop-Location
It "the '-Stream' dynamic parameter should not be visible to get-command in the function provider" -Skip:(!$IsWindows) {
try {
Push-Location -Path function:
{ Get-Command Clear-Content -Stream $streamName } |
Should -Throw -ErrorId "NamedParameterNotFound,Microsoft.PowerShell.Commands.GetCommandCommand"
}
finally {
Pop-Location
}
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -221,25 +221,27 @@ Describe "Get-Content" -Tags "CI" {
$expected = 'He', 'o,', '', 'Wor', "d${nl}He", 'o2,', '', 'Wor', "d2${nl}"
for ($i = 0; $i -lt $result.Length ; $i++) { $result[$i] | Should -BeExactly $expected[$i]}
}

Context "Alternate Data Stream support on Windows" {
It "Should support NTFS streams using colon syntax" -Skip:(!$IsWindows) {
Set-Content "${testPath}:Stream" -Value "Foo"
{ Test-Path "${testPath}:Stream" | Should -Throw -ErrorId "ItemExistsNotSupportedError,Microsoft.PowerShell.Commands,TestPathCommand" }
Get-Content "${testPath}:Stream" | Should -BeExactly "Foo"
Get-Content $testPath | Should -BeExactly $testString
}

It "Should support NTFS streams using colon syntax" -Skip:(!$IsWindows) {
Set-Content "${testPath}:Stream" -Value "Foo"
{ Test-Path "${testPath}:Stream" | Should -Throw -ErrorId "ItemExistsNotSupportedError,Microsoft.PowerShell.Commands,TestPathCommand" }
Get-Content "${testPath}:Stream" | Should -BeExactly "Foo"
Get-Content $testPath | Should -BeExactly $testString
}

It "Should support NTFS streams using -Stream" -Skip:(!$IsWindows) {
Set-Content -Path $testPath -Stream hello -Value World
Get-Content -Path $testPath | Should -BeExactly $testString
Get-Content -Path $testPath -Stream hello | Should -BeExactly "World"
$item = Get-Item -Path $testPath -Stream hello
$item | Should -BeOfType System.Management.Automation.Internal.AlternateStreamData
$item.Stream | Should -BeExactly "hello"
Clear-Content -Path $testPath -Stream hello
Get-Content -Path $testPath -Stream hello | Should -BeNullOrEmpty
Remove-Item -Path $testPath -Stream hello
{ Get-Content -Path $testPath -Stream hello | Should -Throw -ErrorId "GetContentReaderFileNotFoundError,Microsoft.PowerShell.Commands.GetContentCommand" }
It "Should support NTFS streams using -Stream" -Skip:(!$IsWindows) {
Set-Content -Path $testPath -Stream hello -Value World
Get-Content -Path $testPath | Should -BeExactly $testString
Get-Content -Path $testPath -Stream hello | Should -BeExactly "World"
$item = Get-Item -Path $testPath -Stream hello
$item | Should -BeOfType System.Management.Automation.Internal.AlternateStreamData
$item.Stream | Should -BeExactly "hello"
Clear-Content -Path $testPath -Stream hello
Get-Content -Path $testPath -Stream hello | Should -BeNullOrEmpty
Remove-Item -Path $testPath -Stream hello
{ Get-Content -Path $testPath -Stream hello -ErrorAction stop} | Should -Throw -ErrorId "GetContentReaderFileNotFoundError,Microsoft.PowerShell.Commands.GetContentCommand"
}
}

It "Should support colons in filename on Linux/Mac" -Skip:($IsWindows) {
Expand Down
Loading