From 31be7788ccf800886b330fa476ac256945ca7988 Mon Sep 17 00:00:00 2001 From: Travis Plunk Date: Wed, 20 Nov 2019 11:39:52 -0800 Subject: [PATCH 01/13] code for unblock-file on macOS --- .../commands/utility/UnblockFile.cs | 51 ++++++- .../resources/UnblockFileStrings.resx | 126 ++++++++++++++++++ .../Microsoft.PowerShell.Utility.psd1 | 2 +- 3 files changed, 175 insertions(+), 4 deletions(-) create mode 100644 src/Microsoft.PowerShell.Commands.Utility/resources/UnblockFileStrings.resx diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/UnblockFile.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/UnblockFile.cs index 0f1ff4af371..21c4f9b8f1b 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/UnblockFile.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/UnblockFile.cs @@ -1,18 +1,19 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -#if !UNIX - #region Using directives using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.ComponentModel; +using System.Diagnostics; using System.Diagnostics.CodeAnalysis; +using System.Globalization; using System.IO; using System.Management.Automation; using System.Management.Automation.Internal; +using System.Runtime.InteropServices; #endregion @@ -23,6 +24,12 @@ namespace Microsoft.PowerShell.Commands HelpUri = "https://go.microsoft.com/fwlink/?LinkID=217450")] public sealed class UnblockFileCommand : PSCmdlet { + +#if UNIX + private const string MacBlockAttribute = "com.apple.quarantine"; + private const int RemovexattrFollowSymLink = 0; +#endif + /// /// The path of the file to unblock. /// @@ -67,6 +74,7 @@ public string[] LiteralPath /// protected override void ProcessRecord() { +#if !UNIX List pathsToProcess = new List(); ProviderInfo provider = null; @@ -128,8 +136,30 @@ protected override void ProcessRecord() } } } +#else + if (!RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) + { + string errorMessage = UnblockFileStrings.LinuxNotSupported; + Exception e = new NotImplementedException(errorMessage); + ThrowTerminatingError(new ErrorRecord(e, "LinuxNotSupported", ErrorCategory.NotImplemented,null)); + return; + } + + foreach (string path in _paths) + { + UInt32 result = removexattr(path,MacBlockAttribute,RemovexattrFollowSymLink); + if(result != 0) + { + string errorMessage = string.Format(CultureInfo.CurrentUICulture, UnblockFileStrings.UnblockError, path); + Exception e = new InvalidOperationException(errorMessage); + WriteError(new ErrorRecord(e, "UnblockError", ErrorCategory.InvalidResult,path)); + } + } + +#endif } +#if !UNIX /// /// IsValidFileForUnblocking is a helper method used to validate if /// the supplied file path has to be considered for unblocking. @@ -163,6 +193,21 @@ private bool IsValidFileForUnblocking(string resolvedpath) return isValidUnblockableFile; } +#else + // Ansi means UTF8 on Unix + // https://developer.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/man2/removexattr.2.html + [DllImport("libc", SetLastError = true, EntryPoint = "removexattr", CharSet = CharSet.Ansi)] + private static extern UInt32 removexattr(string path, string name, int options); +#endif + private ErrorRecord NewError(string errorId, string resourceId, object targetObject, ErrorCategory category = ErrorCategory.InvalidOperation, params object[] args) + { + ErrorDetails details = new ErrorDetails(this.GetType().Assembly, "UnblockFileStrings", resourceId, args); + ErrorRecord errorRecord = new ErrorRecord( + new InvalidOperationException(details.Message), + errorId, + category, + targetObject); + return errorRecord; + } } } -#endif diff --git a/src/Microsoft.PowerShell.Commands.Utility/resources/UnblockFileStrings.resx b/src/Microsoft.PowerShell.Commands.Utility/resources/UnblockFileStrings.resx new file mode 100644 index 00000000000..8abc3a0298b --- /dev/null +++ b/src/Microsoft.PowerShell.Commands.Utility/resources/UnblockFileStrings.resx @@ -0,0 +1,126 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + The cmdlet does not support Linux. + + + There was an error unblocking {0}, the file may not be blocked. + + diff --git a/src/Modules/Unix/Microsoft.PowerShell.Utility/Microsoft.PowerShell.Utility.psd1 b/src/Modules/Unix/Microsoft.PowerShell.Utility/Microsoft.PowerShell.Utility.psd1 index da57841b21d..41705fb7bd7 100644 --- a/src/Modules/Unix/Microsoft.PowerShell.Utility/Microsoft.PowerShell.Utility.psd1 +++ b/src/Modules/Unix/Microsoft.PowerShell.Utility/Microsoft.PowerShell.Utility.psd1 @@ -25,7 +25,7 @@ CmdletsToExport = @( 'Get-TraceSource', 'Set-TraceSource', 'Add-Type', 'Get-TypeData', 'Remove-TypeData', 'Update-TypeData', 'Get-UICulture', 'Get-Unique', 'Get-Uptime', 'Clear-Variable', 'Get-Variable', 'New-Variable', 'Remove-Variable', 'Set-Variable', 'Get-Verb', 'Write-Verbose', 'Write-Warning', 'Invoke-WebRequest', - 'Format-Wide', 'ConvertTo-Xml', 'Select-Xml', 'Get-Error', 'Update-List' + 'Format-Wide', 'ConvertTo-Xml', 'Select-Xml', 'Get-Error', 'Update-List', 'Unblock-File' ) FunctionsToExport = @() AliasesToExport = @('fhx') From a98e089ee061339b069237e159f2a4106668136a Mon Sep 17 00:00:00 2001 From: Travis Plunk Date: Wed, 20 Nov 2019 12:31:10 -0800 Subject: [PATCH 02/13] Add and update tests --- .../commands/utility/UnblockFile.cs | 8 +- .../Unblock-File.Tests.ps1 | 194 ++++++++++++++---- .../engine/Basic/DefaultCommands.Tests.ps1 | 2 +- 3 files changed, 154 insertions(+), 50 deletions(-) diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/UnblockFile.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/UnblockFile.cs index 21c4f9b8f1b..ce27ecccb49 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/UnblockFile.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/UnblockFile.cs @@ -74,7 +74,6 @@ public string[] LiteralPath /// protected override void ProcessRecord() { -#if !UNIX List pathsToProcess = new List(); ProviderInfo provider = null; @@ -120,6 +119,7 @@ protected override void ProcessRecord() } } } +#if !UNIX // Unblock files foreach (string path in pathsToProcess) @@ -145,7 +145,7 @@ protected override void ProcessRecord() return; } - foreach (string path in _paths) + foreach (string path in pathsToProcess) { UInt32 result = removexattr(path,MacBlockAttribute,RemovexattrFollowSymLink); if(result != 0) @@ -159,7 +159,6 @@ protected override void ProcessRecord() #endif } -#if !UNIX /// /// IsValidFileForUnblocking is a helper method used to validate if /// the supplied file path has to be considered for unblocking. @@ -193,7 +192,8 @@ private bool IsValidFileForUnblocking(string resolvedpath) return isValidUnblockableFile; } -#else + +#if UNIX // Ansi means UTF8 on Unix // https://developer.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/man2/removexattr.2.html [DllImport("libc", SetLastError = true, EntryPoint = "removexattr", CharSet = CharSet.Ansi)] diff --git a/test/powershell/Modules/Microsoft.PowerShell.Utility/Unblock-File.Tests.ps1 b/test/powershell/Modules/Microsoft.PowerShell.Utility/Unblock-File.Tests.ps1 index a62d38d4a77..f13be3c9d81 100644 --- a/test/powershell/Modules/Microsoft.PowerShell.Utility/Unblock-File.Tests.ps1 +++ b/test/powershell/Modules/Microsoft.PowerShell.Utility/Unblock-File.Tests.ps1 @@ -1,69 +1,173 @@ # Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. -function Test-UnblockFile { - { Get-Content -Path $testfilepath -Stream Zone.Identifier -ErrorAction Stop | Out-Null } | - Should -Throw -ErrorId "GetContentReaderFileNotFoundError,Microsoft.PowerShell.Commands.GetContentCommand" -} Describe "Unblock-File" -Tags "CI" { - BeforeAll { - if ( ! $IsWindows ) - { - $origDefaults = $PSDefaultParameterValues.Clone() - $PSDefaultParameterValues['it:skip'] = $true + Context "Windows" { + BeforeAll { + function Test-UnblockFile { + { Get-Content -Path $testfilepath -Stream Zone.Identifier -ErrorAction Stop | Out-Null } | + Should -Throw -ErrorId "GetContentReaderFileNotFoundError,Microsoft.PowerShell.Commands.GetContentCommand" + } + if ( ! $IsWindows ) + { + $origDefaults = $PSDefaultParameterValues.Clone() + $PSDefaultParameterValues['it:skip'] = $true - } else { - $testfilepath = Join-Path -Path $TestDrive -ChildPath testunblockfile.ttt + } else { + $testfilepath = Join-Path -Path $TestDrive -ChildPath testunblockfile.ttt + } } - } - AfterAll { - if ( ! $IsWindows ){ - $global:PSDefaultParameterValues = $origDefaults + AfterAll { + if ( ! $IsWindows ){ + $global:PSDefaultParameterValues = $origDefaults + } } - } - BeforeEach { - if ( $IsWindows ){ - Set-Content -Value "[ZoneTransfer]`r`nZoneId=4" -Path $testfilepath -Stream Zone.Identifier + BeforeEach { + if ( $IsWindows ){ + Set-Content -Value "[ZoneTransfer]`r`nZoneId=4" -Path $testfilepath -Stream Zone.Identifier + } } - } - It "With '-Path': no file exist" { - { Unblock-File -Path nofileexist.ttt -ErrorAction Stop } | Should -Throw -ErrorId "FileNotFound,Microsoft.PowerShell.Commands.UnblockFileCommand" - } + It "With '-Path': no file exist" { + { Unblock-File -Path nofileexist.ttt -ErrorAction Stop } | Should -Throw -ErrorId "FileNotFound,Microsoft.PowerShell.Commands.UnblockFileCommand" + } - It "With '-LiteralPath': no file exist" { - { Unblock-File -LiteralPath nofileexist.ttt -ErrorAction Stop } | Should -Throw -ErrorId "FileNotFound,Microsoft.PowerShell.Commands.UnblockFileCommand" - } + It "With '-LiteralPath': no file exist" { + { Unblock-File -LiteralPath nofileexist.ttt -ErrorAction Stop } | Should -Throw -ErrorId "FileNotFound,Microsoft.PowerShell.Commands.UnblockFileCommand" + } + + It "With '-Path': file exist" { + Unblock-File -Path $testfilepath + Test-UnblockFile + + # If a file is not blocked we silently return without an error. + { Unblock-File -Path $testfilepath -ErrorAction Stop } | Should -Not -Throw + } - It "With '-Path': file exist" { - Unblock-File -Path $testfilepath - Test-UnblockFile + It "With '-LiteralPath': file exist" { + Unblock-File -LiteralPath $testfilepath + Test-UnblockFile + } + + It "Write an error if a file is read only" { + $TestFile = Join-Path $TestDrive "testfileunlock.ps1" + Set-Content -Path $TestFile -value 'test' + $ZoneIdentifier = { + [ZoneTransfer] + ZoneId=3 + } + Set-Content -Path $TestFile -Value $ZoneIdentifier -Stream 'Zone.Identifier' + Set-ItemProperty -Path $TestFile -Name IsReadOnly -Value $True + + $TestFileCreated = Get-ChildItem $TestFile + $TestFileCreated.IsReadOnly | Should -BeTrue - # If a file is not blocked we silently return without an error. - { Unblock-File -Path $testfilepath -ErrorAction Stop } | Should -Not -Throw + { Unblock-File -LiteralPath $TestFile -ErrorAction Stop } | Should -Throw -ErrorId "RemoveItemUnableToAccessFile,Microsoft.PowerShell.Commands.UnblockFileCommand" + } } - It "With '-LiteralPath': file exist" { - Unblock-File -LiteralPath $testfilepath - Test-UnblockFile + Context "macOS" { + BeforeAll { + function Test-UnblockFile { + $result = (xattr $testfilepath | Select-String 'com.apple.com.quarantine') + $result | Should -BeNullOrEmpty + } + + if ( ! $IsMacOS ) + { + $origDefaults = $PSDefaultParameterValues.Clone() + $PSDefaultParameterValues['it:skip'] = $true + + } else { + $testfilepath = Join-Path -Path $TestDrive -ChildPath testunblockfile.ttt + New-Item -Path $testfilepath -ItemType File + } + } + + AfterAll { + if ( ! $IsMacOS ){ + $global:PSDefaultParameterValues = $origDefaults + } + } + + BeforeEach { + if ( $IsMacOS ){ + xattr -w com.apple.quarantine test $testfilepath + } + } + + It "With '-Path': no file exist" { + { Unblock-File -Path nofileexist.ttt -ErrorAction Stop } | Should -Throw -ErrorId "FileNotFound,Microsoft.PowerShell.Commands.UnblockFileCommand" + } + + It "With '-LiteralPath': no file exist" { + { Unblock-File -LiteralPath nofileexist.ttt -ErrorAction Stop } | Should -Throw -ErrorId "FileNotFound,Microsoft.PowerShell.Commands.UnblockFileCommand" + } + + # the error code in not unique + # cannot suppress the error if it's already unblocked. + It "With '-Path': file exist" -Pending { + Unblock-File -Path $testfilepath + Test-UnblockFile + + # If a file is not blocked we silently return without an error. + { Unblock-File -Path $testfilepath -ErrorAction Stop } | Should -Not -Throw + } + + It "With '-LiteralPath': file exist" { + Unblock-File -LiteralPath $testfilepath + Test-UnblockFile + } + + It "Write an error if a file is read only" { + $TestFile = Join-Path $TestDrive "testfileunlock.ps1" + Set-Content -Path $TestFile -value 'test' + xattr -w com.apple.quarantine test $TestFile + Set-ItemProperty -Path $TestFile -Name IsReadOnly -Value $True + + $TestFileCreated = Get-ChildItem $TestFile + $TestFileCreated.IsReadOnly | Should -BeTrue + + { Unblock-File -LiteralPath $TestFile -ErrorAction Stop } | Should -Throw -ErrorId "UnblockError,Microsoft.PowerShell.Commands.UnblockFileCommand" + } } + Context "Linux" { + BeforeAll { + if ( ! $IsLinux ) + { + $origDefaults = $PSDefaultParameterValues.Clone() + $PSDefaultParameterValues['it:skip'] = $true + + } else { + $testfilepath = Join-Path -Path $TestDrive -ChildPath testunblockfile.ttt + New-Item -Path $testfilepath -ItemType File + } + } - It "Write an error if a file is read only" { - $TestFile = Join-Path $TestDrive "testfileunlock.ps1" - Set-Content -Path $TestFile -value 'test' - $ZoneIdentifier = { - [ZoneTransfer] - ZoneId=3 + AfterAll { + if ( ! $IsLinux ){ + $global:PSDefaultParameterValues = $origDefaults + } } - Set-Content -Path $TestFile -Value $ZoneIdentifier -Stream 'Zone.Identifier' - Set-ItemProperty -Path $TestFile -Name IsReadOnly -Value $True - $TestFileCreated = Get-ChildItem $TestFile - $TestFileCreated.IsReadOnly | Should -BeTrue - { Unblock-File -LiteralPath $TestFile -ErrorAction Stop } | Should -Throw -ErrorId "RemoveItemUnableToAccessFile,Microsoft.PowerShell.Commands.UnblockFileCommand" + It "With '-Path': no file exist" { + { Unblock-File -Path nofileexist.ttt -ErrorAction Stop } | Should -Throw -ErrorId "LinuxNotSupported,Microsoft.PowerShell.Commands.UnblockFileCommand" + } + + It "With '-LiteralPath': no file exist" { + { Unblock-File -LiteralPath nofileexist.ttt -ErrorAction Stop } | Should -Throw -ErrorId "LinuxNotSupported,Microsoft.PowerShell.Commands.UnblockFileCommand" + } + + It "With '-LiteralPath': file exist" { + { Unblock-File -LiteralPath $testfilepath -ErrorAction Stop } | Should -Throw -ErrorId "LinuxNotSupported,Microsoft.PowerShell.Commands.UnblockFileCommand" + } + + It "With '-Path': file exist" { + { Unblock-File -Path $testfilepath -ErrorAction Stop } | Should -Throw -ErrorId "LinuxNotSupported,Microsoft.PowerShell.Commands.UnblockFileCommand" + } } } diff --git a/test/powershell/engine/Basic/DefaultCommands.Tests.ps1 b/test/powershell/engine/Basic/DefaultCommands.Tests.ps1 index 8e1bffaf40d..08e7ee9b3a9 100644 --- a/test/powershell/engine/Basic/DefaultCommands.Tests.ps1 +++ b/test/powershell/engine/Basic/DefaultCommands.Tests.ps1 @@ -468,7 +468,7 @@ Describe "Verify approved aliases list" -Tags "CI" { "Cmdlet", "Test-PSSessionConfigurationFile", "", $($FullCLR -or $CoreWindows ), "", "", "None" "Cmdlet", "Test-WSMan", "", $($FullCLR -or $CoreWindows ), "", "", "None" "Cmdlet", "Trace-Command", "", $($FullCLR -or $CoreWindows -or $CoreUnix), "", "", "None" -"Cmdlet", "Unblock-File", "", $($FullCLR -or $CoreWindows ), "", "", "Medium" +"Cmdlet", "Unblock-File", "", $($FullCLR -or $CoreWindows -or $CoreUnix), "", "", "Medium" "Cmdlet", "Undo-Transaction", "", $($FullCLR ), "", "", "" "Cmdlet", "Unprotect-CmsMessage", "", $($FullCLR -or $CoreWindows ), "", "", "None" "Cmdlet", "Unregister-Event", "", $($FullCLR -or $CoreWindows -or $CoreUnix), "", "", "Medium" From ef87a3da4746fa824a831f006799e426207f2090 Mon Sep 17 00:00:00 2001 From: Travis Plunk Date: Wed, 20 Nov 2019 13:00:16 -0800 Subject: [PATCH 03/13] fix linux tests --- .../Microsoft.PowerShell.Utility/Unblock-File.Tests.ps1 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/powershell/Modules/Microsoft.PowerShell.Utility/Unblock-File.Tests.ps1 b/test/powershell/Modules/Microsoft.PowerShell.Utility/Unblock-File.Tests.ps1 index f13be3c9d81..d26d8fcade0 100644 --- a/test/powershell/Modules/Microsoft.PowerShell.Utility/Unblock-File.Tests.ps1 +++ b/test/powershell/Modules/Microsoft.PowerShell.Utility/Unblock-File.Tests.ps1 @@ -155,11 +155,11 @@ Describe "Unblock-File" -Tags "CI" { It "With '-Path': no file exist" { - { Unblock-File -Path nofileexist.ttt -ErrorAction Stop } | Should -Throw -ErrorId "LinuxNotSupported,Microsoft.PowerShell.Commands.UnblockFileCommand" + { Unblock-File -Path nofileexist.ttt -ErrorAction Stop } | Should -Throw -ErrorId "FileNotFound,Microsoft.PowerShell.Commands.UnblockFileCommand" } It "With '-LiteralPath': no file exist" { - { Unblock-File -LiteralPath nofileexist.ttt -ErrorAction Stop } | Should -Throw -ErrorId "LinuxNotSupported,Microsoft.PowerShell.Commands.UnblockFileCommand" + { Unblock-File -LiteralPath nofileexist.ttt -ErrorAction Stop } | Should -Throw -ErrorId "FileNotFound,Microsoft.PowerShell.Commands.UnblockFileCommand" } It "With '-LiteralPath': file exist" { From 088dd3b9fac07ba5ab3058f556004a8d83ae813d Mon Sep 17 00:00:00 2001 From: Travis Plunk Date: Wed, 20 Nov 2019 13:55:32 -0800 Subject: [PATCH 04/13] fix unimplemented test --- .../Microsoft.PowerShell.Utility/Unimplemented-Cmdlet.Tests.ps1 | 1 - 1 file changed, 1 deletion(-) diff --git a/test/powershell/Modules/Microsoft.PowerShell.Utility/Unimplemented-Cmdlet.Tests.ps1 b/test/powershell/Modules/Microsoft.PowerShell.Utility/Unimplemented-Cmdlet.Tests.ps1 index ea19c7d4130..feaf1b0548f 100644 --- a/test/powershell/Modules/Microsoft.PowerShell.Utility/Unimplemented-Cmdlet.Tests.ps1 +++ b/test/powershell/Modules/Microsoft.PowerShell.Utility/Unimplemented-Cmdlet.Tests.ps1 @@ -3,7 +3,6 @@ Describe "Unimplemented Utility Cmdlet Tests" -Tags "CI" { $Commands = @( - "Unblock-File", "ConvertFrom-SddlString" ) From ad12b3460e98335c27e7c361d21e6a6397c1edd2 Mon Sep 17 00:00:00 2001 From: Travis Plunk Date: Wed, 20 Nov 2019 15:11:59 -0800 Subject: [PATCH 05/13] make unblock not fail if already unblocked --- .../commands/utility/UnblockFile.cs | 48 +++++++++++++++---- 1 file changed, 40 insertions(+), 8 deletions(-) diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/UnblockFile.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/UnblockFile.cs index ce27ecccb49..17d6d805ff3 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/UnblockFile.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/UnblockFile.cs @@ -24,7 +24,6 @@ namespace Microsoft.PowerShell.Commands HelpUri = "https://go.microsoft.com/fwlink/?LinkID=217450")] public sealed class UnblockFileCommand : PSCmdlet { - #if UNIX private const string MacBlockAttribute = "com.apple.quarantine"; private const int RemovexattrFollowSymLink = 0; @@ -147,12 +146,15 @@ protected override void ProcessRecord() foreach (string path in pathsToProcess) { - UInt32 result = removexattr(path,MacBlockAttribute,RemovexattrFollowSymLink); - if(result != 0) + if(IsBlocked(path)) { - string errorMessage = string.Format(CultureInfo.CurrentUICulture, UnblockFileStrings.UnblockError, path); - Exception e = new InvalidOperationException(errorMessage); - WriteError(new ErrorRecord(e, "UnblockError", ErrorCategory.InvalidResult,path)); + UInt32 result = RemoveXattr(path,MacBlockAttribute,RemovexattrFollowSymLink); + if(result != 0) + { + string errorMessage = string.Format(CultureInfo.CurrentUICulture, UnblockFileStrings.UnblockError, path); + Exception e = new InvalidOperationException(errorMessage); + WriteError(new ErrorRecord(e, "UnblockError", ErrorCategory.InvalidResult,path)); + } } } @@ -194,10 +196,40 @@ private bool IsValidFileForUnblocking(string resolvedpath) } #if UNIX + private bool IsBlocked(string path) + { + uint valueSize = 1024; + IntPtr value = Marshal.AllocHGlobal(1024); + string valueStr = string.Empty; + try { + var resultSize = GetXattr(path, MacBlockAttribute, value, valueSize, 0, RemovexattrFollowSymLink); + + if(resultSize != -1) + { + valueStr = Marshal.PtrToStringUTF8(value, (int)resultSize); + } + + return resultSize !=-1; + } + finally + { + Marshal.FreeHGlobal(value); + } + } + // Ansi means UTF8 on Unix - // https://developer.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/man2/removexattr.2.html + // https://developer.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/man2/RemoveXattr.2.html [DllImport("libc", SetLastError = true, EntryPoint = "removexattr", CharSet = CharSet.Ansi)] - private static extern UInt32 removexattr(string path, string name, int options); + private static extern UInt32 RemoveXattr(string path, string name, int options); + + [DllImport("libc", EntryPoint = "getxattr", CharSet = CharSet.Ansi)] + private static extern long GetXattr( + [MarshalAs(UnmanagedType.LPStr)] string path, + [MarshalAs(UnmanagedType.LPStr)] string name, + IntPtr value, + ulong size, + uint position, + int options); #endif private ErrorRecord NewError(string errorId, string resourceId, object targetObject, ErrorCategory category = ErrorCategory.InvalidOperation, params object[] args) { From 7823022b952749b6f8c6c5f95984b8b92942f629 Mon Sep 17 00:00:00 2001 From: Travis Plunk Date: Wed, 20 Nov 2019 15:12:30 -0800 Subject: [PATCH 06/13] Get rid of code to get value string of attribute --- .../commands/utility/UnblockFile.cs | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/UnblockFile.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/UnblockFile.cs index 17d6d805ff3..e99c3a0455a 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/UnblockFile.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/UnblockFile.cs @@ -203,12 +203,6 @@ private bool IsBlocked(string path) string valueStr = string.Empty; try { var resultSize = GetXattr(path, MacBlockAttribute, value, valueSize, 0, RemovexattrFollowSymLink); - - if(resultSize != -1) - { - valueStr = Marshal.PtrToStringUTF8(value, (int)resultSize); - } - return resultSize !=-1; } finally From a3c7d80a03a5b3a2a5e8704b0ed4e7a480707783 Mon Sep 17 00:00:00 2001 From: Travis Plunk Date: Wed, 20 Nov 2019 15:12:44 -0800 Subject: [PATCH 07/13] enable pending test --- .../Microsoft.PowerShell.Utility/Unblock-File.Tests.ps1 | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/powershell/Modules/Microsoft.PowerShell.Utility/Unblock-File.Tests.ps1 b/test/powershell/Modules/Microsoft.PowerShell.Utility/Unblock-File.Tests.ps1 index d26d8fcade0..243bdbfa106 100644 --- a/test/powershell/Modules/Microsoft.PowerShell.Utility/Unblock-File.Tests.ps1 +++ b/test/powershell/Modules/Microsoft.PowerShell.Utility/Unblock-File.Tests.ps1 @@ -95,7 +95,7 @@ Describe "Unblock-File" -Tags "CI" { BeforeEach { if ( $IsMacOS ){ - xattr -w com.apple.quarantine test $testfilepath + xattr -w com.apple.quarantine '0081;5dd5c373;Microsoft Edge;1A9A933D-619A-4036-BAF3-17A7966A1BA8' $testfilepath } } @@ -109,7 +109,7 @@ Describe "Unblock-File" -Tags "CI" { # the error code in not unique # cannot suppress the error if it's already unblocked. - It "With '-Path': file exist" -Pending { + It "With '-Path': file exist" { Unblock-File -Path $testfilepath Test-UnblockFile @@ -125,7 +125,7 @@ Describe "Unblock-File" -Tags "CI" { It "Write an error if a file is read only" { $TestFile = Join-Path $TestDrive "testfileunlock.ps1" Set-Content -Path $TestFile -value 'test' - xattr -w com.apple.quarantine test $TestFile + xattr -w com.apple.quarantine '0081;5dd5c373;Microsoft Edge;1A9A933D-619A-4036-BAF3-17A7966A1BA8' $TestFile Set-ItemProperty -Path $TestFile -Name IsReadOnly -Value $True $TestFileCreated = Get-ChildItem $TestFile From 8adccdcafe268b4ebe86067f186c3528778971a5 Mon Sep 17 00:00:00 2001 From: Travis Plunk Date: Wed, 20 Nov 2019 15:24:09 -0800 Subject: [PATCH 08/13] Combine macOS and Windows tests --- .../resources/UnblockFileStrings.resx | 2 +- .../Unblock-File.Tests.ps1 | 110 +++++------------- 2 files changed, 33 insertions(+), 79 deletions(-) diff --git a/src/Microsoft.PowerShell.Commands.Utility/resources/UnblockFileStrings.resx b/src/Microsoft.PowerShell.Commands.Utility/resources/UnblockFileStrings.resx index 8abc3a0298b..b2af7eba67e 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/resources/UnblockFileStrings.resx +++ b/src/Microsoft.PowerShell.Commands.Utility/resources/UnblockFileStrings.resx @@ -121,6 +121,6 @@ The cmdlet does not support Linux. - There was an error unblocking {0}, the file may not be blocked. + There was an error unblocking {0}. diff --git a/test/powershell/Modules/Microsoft.PowerShell.Utility/Unblock-File.Tests.ps1 b/test/powershell/Modules/Microsoft.PowerShell.Utility/Unblock-File.Tests.ps1 index 243bdbfa106..81aa3ced27f 100644 --- a/test/powershell/Modules/Microsoft.PowerShell.Utility/Unblock-File.Tests.ps1 +++ b/test/powershell/Modules/Microsoft.PowerShell.Utility/Unblock-File.Tests.ps1 @@ -3,13 +3,33 @@ Describe "Unblock-File" -Tags "CI" { - Context "Windows" { + Context "Windows and macOS" { BeforeAll { - function Test-UnblockFile { - { Get-Content -Path $testfilepath -Stream Zone.Identifier -ErrorAction Stop | Out-Null } | + + if ($IsWindows) { + function Test-UnblockFile { + { Get-Content -Path $testfilepath -Stream Zone.Identifier -ErrorAction Stop | Out-Null } | Should -Throw -ErrorId "GetContentReaderFileNotFoundError,Microsoft.PowerShell.Commands.GetContentCommand" + } + function Block-File { + param($path) + Set-Content -Value "[ZoneTransfer]`r`nZoneId=4" -Path $path -Stream Zone.Identifier + } + } + else { + function Test-UnblockFile { + $result = (xattr $testfilepath | Select-String 'com.apple.com.quarantine') + $result | Should -BeNullOrEmpty + } + function Block-File { + param($path) + Set-Content -Path $path -value 'test' + xattr -w com.apple.quarantine '0081;5dd5c373;Microsoft Edge;1A9A933D-619A-4036-BAF3-17A7966A1BA8' $path + + } } - if ( ! $IsWindows ) + + if ( $IsLinux ) { $origDefaults = $PSDefaultParameterValues.Clone() $PSDefaultParameterValues['it:skip'] = $true @@ -20,15 +40,13 @@ Describe "Unblock-File" -Tags "CI" { } AfterAll { - if ( ! $IsWindows ){ + if ( $IsLinux ){ $global:PSDefaultParameterValues = $origDefaults } } BeforeEach { - if ( $IsWindows ){ - Set-Content -Value "[ZoneTransfer]`r`nZoneId=4" -Path $testfilepath -Stream Zone.Identifier - } + Block-File -Path $testfilepath } It "With '-Path': no file exist" { @@ -54,86 +72,22 @@ Describe "Unblock-File" -Tags "CI" { It "Write an error if a file is read only" { $TestFile = Join-Path $TestDrive "testfileunlock.ps1" - Set-Content -Path $TestFile -value 'test' - $ZoneIdentifier = { - [ZoneTransfer] - ZoneId=3 - } - Set-Content -Path $TestFile -Value $ZoneIdentifier -Stream 'Zone.Identifier' + Block-File -Path $TestFile Set-ItemProperty -Path $TestFile -Name IsReadOnly -Value $True $TestFileCreated = Get-ChildItem $TestFile $TestFileCreated.IsReadOnly | Should -BeTrue - - { Unblock-File -LiteralPath $TestFile -ErrorAction Stop } | Should -Throw -ErrorId "RemoveItemUnableToAccessFile,Microsoft.PowerShell.Commands.UnblockFileCommand" - } - } - - Context "macOS" { - BeforeAll { - function Test-UnblockFile { - $result = (xattr $testfilepath | Select-String 'com.apple.com.quarantine') - $result | Should -BeNullOrEmpty - } - - if ( ! $IsMacOS ) + if($IsWindows) { - $origDefaults = $PSDefaultParameterValues.Clone() - $PSDefaultParameterValues['it:skip'] = $true - + $expectedError = "RemoveItemUnableToAccessFile,Microsoft.PowerShell.Commands.UnblockFileCommand" } else { - $testfilepath = Join-Path -Path $TestDrive -ChildPath testunblockfile.ttt - New-Item -Path $testfilepath -ItemType File - } - } - - AfterAll { - if ( ! $IsMacOS ){ - $global:PSDefaultParameterValues = $origDefaults + $expectedError = "UnblockError,Microsoft.PowerShell.Commands.UnblockFileCommand" } - } - - BeforeEach { - if ( $IsMacOS ){ - xattr -w com.apple.quarantine '0081;5dd5c373;Microsoft Edge;1A9A933D-619A-4036-BAF3-17A7966A1BA8' $testfilepath - } - } - - It "With '-Path': no file exist" { - { Unblock-File -Path nofileexist.ttt -ErrorAction Stop } | Should -Throw -ErrorId "FileNotFound,Microsoft.PowerShell.Commands.UnblockFileCommand" - } - - It "With '-LiteralPath': no file exist" { - { Unblock-File -LiteralPath nofileexist.ttt -ErrorAction Stop } | Should -Throw -ErrorId "FileNotFound,Microsoft.PowerShell.Commands.UnblockFileCommand" - } - - # the error code in not unique - # cannot suppress the error if it's already unblocked. - It "With '-Path': file exist" { - Unblock-File -Path $testfilepath - Test-UnblockFile - - # If a file is not blocked we silently return without an error. - { Unblock-File -Path $testfilepath -ErrorAction Stop } | Should -Not -Throw - } - - It "With '-LiteralPath': file exist" { - Unblock-File -LiteralPath $testfilepath - Test-UnblockFile - } - - It "Write an error if a file is read only" { - $TestFile = Join-Path $TestDrive "testfileunlock.ps1" - Set-Content -Path $TestFile -value 'test' - xattr -w com.apple.quarantine '0081;5dd5c373;Microsoft Edge;1A9A933D-619A-4036-BAF3-17A7966A1BA8' $TestFile - Set-ItemProperty -Path $TestFile -Name IsReadOnly -Value $True - $TestFileCreated = Get-ChildItem $TestFile - $TestFileCreated.IsReadOnly | Should -BeTrue - - { Unblock-File -LiteralPath $TestFile -ErrorAction Stop } | Should -Throw -ErrorId "UnblockError,Microsoft.PowerShell.Commands.UnblockFileCommand" + { Unblock-File -LiteralPath $TestFile -ErrorAction Stop } | Should -Throw -ErrorId $expectedError } } + Context "Linux" { BeforeAll { if ( ! $IsLinux ) From 9f1694804be6b4813cd723e32bcb53efd51080b3 Mon Sep 17 00:00:00 2001 From: Travis Plunk Date: Thu, 21 Nov 2019 11:36:25 -0800 Subject: [PATCH 09/13] Apply suggestions from code review Co-Authored-By: Steve Lee Co-Authored-By: Ilya --- .../commands/utility/UnblockFile.cs | 12 ++++++------ .../Unblock-File.Tests.ps1 | 4 +++- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/UnblockFile.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/UnblockFile.cs index e99c3a0455a..9e57d780d0a 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/UnblockFile.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/UnblockFile.cs @@ -136,11 +136,11 @@ protected override void ProcessRecord() } } #else - if (!RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) + if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) { string errorMessage = UnblockFileStrings.LinuxNotSupported; Exception e = new NotImplementedException(errorMessage); - ThrowTerminatingError(new ErrorRecord(e, "LinuxNotSupported", ErrorCategory.NotImplemented,null)); + ThrowTerminatingError(new ErrorRecord(e, "LinuxNotSupported", ErrorCategory.NotImplemented, null)); return; } @@ -199,11 +199,11 @@ private bool IsValidFileForUnblocking(string resolvedpath) private bool IsBlocked(string path) { uint valueSize = 1024; - IntPtr value = Marshal.AllocHGlobal(1024); - string valueStr = string.Empty; - try { + IntPtr value = Marshal.AllocHGlobal(valueSize); + try + { var resultSize = GetXattr(path, MacBlockAttribute, value, valueSize, 0, RemovexattrFollowSymLink); - return resultSize !=-1; + return resultSize != -1; } finally { diff --git a/test/powershell/Modules/Microsoft.PowerShell.Utility/Unblock-File.Tests.ps1 b/test/powershell/Modules/Microsoft.PowerShell.Utility/Unblock-File.Tests.ps1 index 81aa3ced27f..09af3784129 100644 --- a/test/powershell/Modules/Microsoft.PowerShell.Utility/Unblock-File.Tests.ps1 +++ b/test/powershell/Modules/Microsoft.PowerShell.Utility/Unblock-File.Tests.ps1 @@ -11,6 +11,7 @@ Describe "Unblock-File" -Tags "CI" { { Get-Content -Path $testfilepath -Stream Zone.Identifier -ErrorAction Stop | Out-Null } | Should -Throw -ErrorId "GetContentReaderFileNotFoundError,Microsoft.PowerShell.Commands.GetContentCommand" } + function Block-File { param($path) Set-Content -Value "[ZoneTransfer]`r`nZoneId=4" -Path $path -Stream Zone.Identifier @@ -21,6 +22,7 @@ Describe "Unblock-File" -Tags "CI" { $result = (xattr $testfilepath | Select-String 'com.apple.com.quarantine') $result | Should -BeNullOrEmpty } + function Block-File { param($path) Set-Content -Path $path -value 'test' @@ -97,7 +99,7 @@ Describe "Unblock-File" -Tags "CI" { } else { $testfilepath = Join-Path -Path $TestDrive -ChildPath testunblockfile.ttt - New-Item -Path $testfilepath -ItemType File + $null = New-Item -Path $testfilepath -ItemType File } } From 760aa10b7dfaaec58760deb8fa80fa71f0c184d1 Mon Sep 17 00:00:00 2001 From: Travis Plunk Date: Wed, 20 Nov 2019 15:31:55 -0800 Subject: [PATCH 10/13] remove unused code --- .../commands/utility/UnblockFile.cs | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/UnblockFile.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/UnblockFile.cs index 9e57d780d0a..1bfc1e6c031 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/UnblockFile.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/UnblockFile.cs @@ -225,15 +225,5 @@ private static extern long GetXattr( uint position, int options); #endif - private ErrorRecord NewError(string errorId, string resourceId, object targetObject, ErrorCategory category = ErrorCategory.InvalidOperation, params object[] args) - { - ErrorDetails details = new ErrorDetails(this.GetType().Assembly, "UnblockFileStrings", resourceId, args); - ErrorRecord errorRecord = new ErrorRecord( - new InvalidOperationException(details.Message), - errorId, - category, - targetObject); - return errorRecord; - } } } From d4f939265ede36bb717c8d74eaed6dc40e845d6c Mon Sep 17 00:00:00 2001 From: Travis Plunk Date: Thu, 21 Nov 2019 11:38:06 -0800 Subject: [PATCH 11/13] format if statement --- .../Microsoft.PowerShell.Utility/Unblock-File.Tests.ps1 | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/test/powershell/Modules/Microsoft.PowerShell.Utility/Unblock-File.Tests.ps1 b/test/powershell/Modules/Microsoft.PowerShell.Utility/Unblock-File.Tests.ps1 index 09af3784129..2b19f370bc2 100644 --- a/test/powershell/Modules/Microsoft.PowerShell.Utility/Unblock-File.Tests.ps1 +++ b/test/powershell/Modules/Microsoft.PowerShell.Utility/Unblock-File.Tests.ps1 @@ -79,8 +79,7 @@ Describe "Unblock-File" -Tags "CI" { $TestFileCreated = Get-ChildItem $TestFile $TestFileCreated.IsReadOnly | Should -BeTrue - if($IsWindows) - { + if ($IsWindows) { $expectedError = "RemoveItemUnableToAccessFile,Microsoft.PowerShell.Commands.UnblockFileCommand" } else { $expectedError = "UnblockError,Microsoft.PowerShell.Commands.UnblockFileCommand" From da3da7ce77fffcaf22279ccf071b405877f4924c Mon Sep 17 00:00:00 2001 From: Travis Plunk Date: Thu, 21 Nov 2019 16:49:44 -0800 Subject: [PATCH 12/13] fix build break --- .../commands/utility/UnblockFile.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/UnblockFile.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/UnblockFile.cs index 1bfc1e6c031..dfda0330d3b 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/UnblockFile.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/UnblockFile.cs @@ -199,7 +199,7 @@ private bool IsValidFileForUnblocking(string resolvedpath) private bool IsBlocked(string path) { uint valueSize = 1024; - IntPtr value = Marshal.AllocHGlobal(valueSize); + IntPtr value = Marshal.AllocHGlobal((int)valueSize); try { var resultSize = GetXattr(path, MacBlockAttribute, value, valueSize, 0, RemovexattrFollowSymLink); From d7c5a178f948873e98407ad3248699c825b70430 Mon Sep 17 00:00:00 2001 From: Travis Plunk Date: Mon, 2 Dec 2019 11:57:53 -0800 Subject: [PATCH 13/13] Address PR comments --- .../commands/utility/UnblockFile.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/UnblockFile.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/UnblockFile.cs index dfda0330d3b..e1d8f86c8ac 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/UnblockFile.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/UnblockFile.cs @@ -131,7 +131,7 @@ protected override void ProcessRecord() } catch (Exception e) { - WriteError(new ErrorRecord(e, "RemoveItemUnableToAccessFile", ErrorCategory.ResourceUnavailable, path)); + WriteError(new ErrorRecord(exception: e, errorId: "RemoveItemUnableToAccessFile", ErrorCategory.ResourceUnavailable, targetObject: path)); } } } @@ -139,8 +139,8 @@ protected override void ProcessRecord() if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) { string errorMessage = UnblockFileStrings.LinuxNotSupported; - Exception e = new NotImplementedException(errorMessage); - ThrowTerminatingError(new ErrorRecord(e, "LinuxNotSupported", ErrorCategory.NotImplemented, null)); + Exception e = new PlatformNotSupportedException(errorMessage); + ThrowTerminatingError(new ErrorRecord(exception: e, errorId: "LinuxNotSupported", ErrorCategory.NotImplemented, targetObject: null)); return; } @@ -148,12 +148,12 @@ protected override void ProcessRecord() { if(IsBlocked(path)) { - UInt32 result = RemoveXattr(path,MacBlockAttribute,RemovexattrFollowSymLink); + UInt32 result = RemoveXattr(path, MacBlockAttribute, RemovexattrFollowSymLink); if(result != 0) { string errorMessage = string.Format(CultureInfo.CurrentUICulture, UnblockFileStrings.UnblockError, path); Exception e = new InvalidOperationException(errorMessage); - WriteError(new ErrorRecord(e, "UnblockError", ErrorCategory.InvalidResult,path)); + WriteError(new ErrorRecord(exception: e, errorId: "UnblockError", ErrorCategory.InvalidResult, targetObject: path)); } } }