8000 Support Invoke-Item -Path <folder> by SteveL-MSFT · Pull Request #4262 · PowerShell/PowerShell · GitHub
[go: up one dir, main page]

Skip to content

Support Invoke-Item -Path <folder> #4262

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

Merged
merged 9 commits into from
Jul 28, 2017
Merged
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
62 changes: 34 additions & 28 deletions src/System.Management.Automation/namespaces/FileSystemProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1312,6 +1312,14 @@ private FileSystemInfo GetFileSystemItem(string path, ref bool isContainer, bool
/// </exception>
protected override void InvokeDefaultAction(string path)
{
#if UNIX
// Error code 13 -- Permission denied
const int NOT_EXECUTABLE = 13;
#else
// Error code 193 -- BAD_EXE_FORMAT (not a valid Win32 application)
const int NOT_EXECUTABLE = 193;
#endif

if (String.IsNullOrEmpty(path))
{
throw PSTraceSource.NewArgumentException("path");
Expand All @@ -1325,21 +1333,34 @@ protected override void InvokeDefaultAction(string path)

if (ShouldProcess(resource, action))
{
System.Diagnostics.Process invokeProcess = new System.Diagnostics.Process();
var invokeProcess = new System.Diagnostics.Process();
invokeProcess.StartInfo.FileName = path;
bool invokeDefaultProgram = false;

try
if (Directory.Exists(path) && !Platform.IsNanoServer && !Platform.IsIoT)
{
// Try Process.Start first.
// - In FullCLR, this is all we need to do.
// - In CoreCLR, this works for executables on Win/Unix platforms
invokeProcess.StartInfo.FileName = path;
invokeProcess.Start();
// Path points to a directory and it's not NanoServer or IoT, so we can opne the file explorer
invokeDefaultProgram = true;
}
#if UNIX
catch (Win32Exception ex) when (ex.NativeErrorCode == 13)
else
{
try
{
// Try Process.Start first. This works for executables on Win/Unix platforms
invokeProcess.Start();
}
catch (Win32Exception ex) when (ex.NativeErrorCode == NOT_EXECUTABLE)
{
// The file is possibly not an executable. If it's headless SKUs, rethrow.
if (Platform.IsNanoServer || Platform.IsIoT) { throw; }
// Otherwise, try invoking the default program that handles this file.
invokeDefaultProgram = true;
}
}

if (invokeDefaultProgram)
{
// Error code 13 -- Permission denied.
// The file is possibly not an executable, so we try invoking the default program that handles this file.
#if UNIX
const string quoteFormat = "\"{0}\"";
invokeProcess.StartInfo.FileName = Platform.IsLinux ? "xdg-open" : /* OS X */ "open";
if (NativeCommandParameterBinder.NeedQuotes(path))
Expand All @@ -1348,25 +1369,10 @@ protected override void InvokeDefaultAction(string path)
}
invokeProcess.StartInfo.Arguments = path;
invokeProcess.Start();
}
#elif CORECLR
catch (Win32Exception ex) when (ex.NativeErrorCode == 193)
{
// Error code 193 -- BAD_EXE_FORMAT (not a valid Win32 application).
// If it's headless SKUs, rethrow.
if (Platform.IsNanoServer || Platform.IsIoT) { throw; }
// If it's full Windows, then try ShellExecute.
ShellExecuteHelper.Start(invokeProcess.StartInfo);
}
#else
finally
{
// Nothing to do in FullCLR.
// This empty 'finally' block is just to match the 'try' block above so that the code can be organized
// in a clean way without too many if/def's.
// Empty finally block will be ignored in release build, so there is no performance concern.
}
ShellExecuteHelper.Start(invokeProcess.StartInfo);
#endif
}
}
} // InvokeDefaultAction

Expand Down
8000
Original file line number Diff line number Diff line change
Expand Up @@ -28,21 +28,23 @@ Describe "Invoke-Item basic tests" -Tags "CI" {

## Run this test only on OSX because redirecting stderr of 'xdg-open' results in weird behavior in our Linux CI,
## causing this test to fail or the build to hang.
It "Should invoke text file '<TestFile>' without error" -Skip:(!$IsOSX) -TestCases $textFileTestCases {
It "Should invoke text file '<TestFile>' without error on Mac" -Skip:(!$IsOSX) -TestCases $textFileTestCases {
param($TestFile)

## Redirect stderr to a file. So if 'open' failed to open the text file, an error
## message from 'open' would be written to the redirection file.
$proc = Start-Process -FilePath $powershell -ArgumentList "-noprofile -c Invoke-Item '$TestFile'" `
-RedirectStandardError $redirectErr `
-PassThru
$proc.WaitForExit(3000) > $null
if (!$proc.HasExited) {
try { $proc.Kill() } catch { }
$expectedTitle = Split-Path $TestFile -Leaf
$beforeCount = [int]('tell application "TextEdit" to count of windows' | osascript)
Invoke-Item -Path $TestFile
$startTime = Get-Date
$title = [String]::Empty
while (((Get-Date) - $startTime).TotalSeconds -lt 10 -and ($title -ne $expectedTitle))
{
Start-Sleep -Milliseconds 100
$title = 'tell application "TextEdit" to get name of front window' | osascript
}
## If the text file was successfully opened, the redirection file should be empty since no error
## message was written to it.
Get-Content $redirectErr -Raw | Should BeNullOrEmpty
$afterCount = [int]('tell application "TextEdit" to count of windows' | osascript)
$afterCount | Should Be ($beforeCount + 1)
$title | Should Be $expectedTitle
"tell application ""TextEdit"" to close window ""$expectedTitle""" | osascript
}
}

Expand All @@ -59,6 +61,100 @@ Describe "Invoke-Item basic tests" -Tags "CI" {
}
Get-Content $redirectFile -Raw | Should Match "usage: ping"
}

Context "Invoke a folder" {
BeforeAll {
$supportedEnvironment = $true
if ($IsLinux)
{
$appFolder = "$HOME/.local/share/applications"
if (Test-Path $appFolder)
{
$mimeDefault = xdg-mime query default inode/directory
Remove-Item $HOME/InvokeItemTest.Success -Force -ErrorAction SilentlyContinue
Set-Content -Path "$appFolder/InvokeItemTest.desktop" -Force -Value @"
[Desktop Entry]
Version=1.0
Name=InvokeItemTest
Comment=Validate Invoke-Item for directory
Exec=/bin/sh -c 'echo %u > ~/InvokeItemTest.Success'
Icon=utilities-terminal
Terminal=true
Type=Application
Categories=Application;
"@
xdg-mime default InvokeItemTest.desktop inode/directory
}
else
{
$supportedEnvironment = $false
Copy link
Collaborator

Choose a reason for hiding this comment

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

What environments we skip and why? Could you please add comment?

Copy link
Member

Choose a reason for hiding this comment

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

Maybe I merged too fast :) I guess this can happen on a Linux without a desktop.

Copy link
Member Author
@SteveL-MSFT SteveL-MSFT Jul 28, 2017

Choose a reason for hiding this comment

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

Correct, Travis-CI, for example

}
}
}

AfterAll {
if ($IsLinux -and $supportedEnvironment)
{
xdg-mime default $mimeDefault inode/directory
Remove-Item $appFolder/InvokeItemTest.desktop -Force -ErrorAction SilentlyContinue
Remove-Item $HOME/InvokeItemTest.Success -Force -ErrorAction SilentlyContinue
}
}

It "Should invoke a folder without error" -Skip:(!$supportedEnvironment) {
if ($IsWindows)
{
$shell = New-Object -ComObject "Shell.Application"
$windows = $shell.Windows()

$before = $windows.Count
Invoke-Item -Path $PSHOME
$startTime = Get-Date
# may take time for explorer to open window
while (((Get-Date) - $startTime).TotalSeconds -lt 10 -and ($windows.Count -eq $before))
{
Start-Sleep -Milliseconds 100
}
$after = $windows.Count

$before + 1 | Should Be $after
$item = $windows.Item($after - 1)
$item.LocationURL | Should Match ($PSHOME -replace '\\', '/')
## close the windows explorer
$item.Quit()
}
elseif ($IsLinux)
{
# validate on Unix by reassociating default app for directories
Invoke-Item -Path $PSHOME
$startTime = Get-Date
# may take time for handler to start
while (((Get-Date) - $startTime).TotalSeconds -lt 10 -and (-not (Test-Path "$HOME/InvokeItemTest.Success")))
{
Start-Sl 7BB5 eep -Milliseconds 100
}
Get-Content $HOME/InvokeItemTest.Success | Should Be $PSHOME
}
else
{
# validate on MacOS by using AppleScript
$beforeCount = [int]('tell application "Finder" to count of windows' | osascript)
Invoke-Item -Path $PSHOME
$startTime = Get-Date
$expectedTitle = Split-Path $PSHOME -Leaf
$title = [String]::Empty
while (((Get-Date) - $startTime).TotalSeconds -lt 10 -and ($title -ne $expectedTitle))
{
Start-Sleep -Milliseconds 100
$title = 'tell application "Finder" to get name of front window' | osascript
}
$afterCount = [int]('tell application "Finder" to count of windows' | osascript)
$afterCount | Should Be ($beforeCount + 1)
$title | Should Be $expectedTitle
'tell application "Finder" to close front window' | osascript
}
}
}
}

Describe "Invoke-Item tests on Windows" -Tags "CI","RequireAdminOnWindows" {
Expand Down
0