Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
31f4d44
`Get-Item directory -stream *` should work (v3)
kyanha Sep 17, 2020
ef27072
Adding tests for positive and negative stream finding
kyanha Sep 18, 2020
188674c
Address @iSazanov's comments, and add a necessary test
kyanha Sep 20, 2020
d3bcf45
Address iSasanova's comments
kyanha Sep 21, 2020
8be94a1
Make the description of a test more precise
kyanha Sep 21, 2020
698dd00
Update test/powershell/Modules/Microsoft.PowerShell.Management/Get-It…
kyanha Sep 22, 2020
4d048f8
Merge branch 'master' of https://github.com/Powershell/Powershell int…
kyanha Sep 28, 2020
62c5284
Doing the rest of the work for alternate data streams
kyanha Sep 30, 2020
60c8d87
Fix a missing colon
kyanha Oct 1, 2020
ccadb16
Apparently, $skipNotWindows is not defined in Add-Content.Tests.ps1.
kyanha Oct 1, 2020
4ae8e38
Fixing conditions for the stream tests in Set-Content.Tests.ps1
kyanha Oct 1, 2020
115ae16
Fixing conditions for stream tests in Clear-Content.Tests.ps1
kyanha Oct 1, 2020
10957b6
Guard a Windows-specific BeforeAll line
kyanha Oct 1, 2020
1b8d1db
Positive tests for alternate data stream behaviors
kyanha Oct 4, 2020
2ecca28
Fixes to non-Windows test runs.
kyanha Oct 4, 2020
6bce44a
Update test/powershell/Modules/Microsoft.PowerShell.Management/Clear-…
kyanha Oct 5, 2020
deae955
Update test/powershell/Modules/Microsoft.PowerShell.Management/Clear-…
kyanha Oct 5, 2020
250d500
Friendlier error message when a filesystem doesn't support ADS
kyanha Oct 5, 2020
3ed7aad
Updated tests for alternate data streams
kyanha Oct 5, 2020
f5029e4
Merge branch 'master' of https://github.com/Powershell/Powershell int…
kyanha Oct 5, 2020
7dbb7c4
Added some comments to explain why directories are sometimes allowed …
kyanha Oct 5, 2020
402630a
Update test/powershell/Modules/Microsoft.PowerShell.Management/Get-It…
kyanha Oct 5, 2020
37a9e11
Update test/powershell/Modules/Microsoft.PowerShell.Management/Get-It…
kyanha Oct 5, 2020
49f5ff1
Update test/powershell/Modules/Microsoft.PowerShell.Management/Get-It…
kyanha Oct 5, 2020
5929b7a
Make a confusing check be an explicit type check
kyanha Oct 5, 2020
451d4b4
Removing redundant test setup
kyanha Oct 6, 2020
a665fb8
Change a test to test for specific type
kyanha Oct 6, 2020
646139c
Update test/powershell/Modules/Microsoft.PowerShell.Management/Remove…
kyanha Oct 8, 2020
124f23f
Update test/powershell/Modules/Microsoft.PowerShell.Management/Remove…
kyanha Oct 8, 2020
fe0e8a8
Update test/powershell/Modules/Microsoft.PowerShell.Management/Get-It…
kyanha Oct 8, 2020
ad8edea
Update test/powershell/Modules/Microsoft.PowerShell.Management/Clear-…
kyanha Oct 8, 2020
5a518a9
Fix a test that an accepted suggestion broke
kyanha Oct 10, 2020
0c45cad
Migrate the error message to FileSystemProviderStrings.resx.
kyanha Oct 10, 2020
cc845c3
Finishing the last batch of changes requested by @iSazonov
kyanha Oct 11, 2020
ccd8283
Merge branch 'master' of https://github.com/Powershell/Powershell int…
kyanha Oct 11, 2020
f4023fa
Merge branch 'master' into issue10570
kyanha Oct 17, 2020
745105a
fixing test file to have fewer comments
kyanha Oct 17, 2020
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
108 changes: 74 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,33 @@ 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;
WriteItemObject(stream, outputPath, isContainer);
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 +6664,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 +6826,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 +6911,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 +6962,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 +8647,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 +8796,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
Original file line number Diff line number Diff line change
Expand Up @@ -112,16 +112,38 @@ Describe "Get-Item" -Tags "CI" {
return
}
$altStreamPath = "$TESTDRIVE/altStream.txt"
$altStreamDirectory = "$TESTDRIVE/altstreamdir"
$noAltStreamDirectory = "$TESTDRIVE/noaltstreamdir"
$stringData = "test data"
$streamName = "test"
$item = New-Item -type file $altStreamPath
$absentStreamName = "noExist"
$null = New-Item -type file $altStreamPath
Set-Content -Path $altStreamPath -Stream $streamName -Value $stringData
$null = New-Item -type directory $altStreamDirectory
cmd.exe /c echo ${stringData} > "${altStreamDirectory}:${streamName}"
$null = New-Item -type directory $noAltStreamDirectory
}
It "Should find an alternate stream if present" -Skip:$skipNotWindows {
It "Should find an alternate stream on a file if present" -Skip:$skipNotWindows {
$result = Get-Item $altStreamPath -Stream $streamName
$result.Length | Should -Be ($stringData.Length + [Environment]::NewLine.Length)
$result.Stream | Should -Be $streamName
}
It "Should error if it cannot find alternate stream on a file if not present" -Skip:$skipNotWindows {
{ Get-Item $altStreamPath -Stream $absentStreamName -ErrorAction Stop } | Should -Throw -ErrorId "AlternateDataStreamNotFound,Microsoft.PowerShell.Commands.GetItemCommand"
}
It "Should find an alternate stream on a directory if present" -Skip:$skipNotWindows {
$result = Get-Item $altStreamDirectory -Stream $streamName
# cmd's echo appends another NewLine to the string it echoes, so we end up with 2 newlines.
$result.Length | Should -Be ($stringData.Length + (2 * [Environment]::NewLine.Length) )
$result.Stream | Should -Be $streamName
}
It "Should not find an alternate stream on a directory if not present" -Skip:$skipNotWindows {
{ Get-Item $noAltStreamDirectory -Stream $absentStreamName -ErrorAction Stop } | Should -Throw -ErrorId "AlternateDataStreamNotFound,Microsoft.PowerShell.Commands.GetItemCommand"
}
It "Should find zero alt streams and not fail on a directory with a wildcard stream name if no alt streams are present" -Skip:$skipNotWindows {
$result = Get-Item $noAltStreamDirectory -Stream * -ErrorAction Stop
$result | Should -Be $null
}
}

Context "Registry Provider" {
Expand Down
Loading