Fix blocking wait when starting file associated with a Windows application by SteveL-MSFT · Pull Request #13750 · PowerShell/PowerShell

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

Conversation

SteveL-MSFT

PR Summary

Change #13481 modified some logic on how console apps are detected. The problem in this situation is:

  1. user starts diskmgmt.msc
  2. check if exe, this is not an exe so set _isWindowsApplication = false
  3. check if associated with an exe
  4. finds mmc.exe
  5. check if exe, this is an exe, so launch this with previous "command" as the argument
  6. check _isWindowsApplication to determine if we block waiting for exe to finish, since this value was cached and not updated on step 5, it treats as console exe and waits

Fix is to change CheckIfConsoleApplication() to be non-static so that the second time it's called it updates _isWindowsApplication to point to the new exe and not the previous file.

Tested manually. Since CI won't have a Windows app associated with a file, can't create a test case.

PR Context

Fix #13744

PR Checklist

@iSazonov

@SteveL-MSFT I think we should simply revert a change in line 557
from
_isRunningInBackground = IsWindowsApplication;
to
_isRunningInBackground = IsWindowsApplication(_nativeProcess.StartInfo.FileName);

As for test, you could see last test in Invoke-Item.Tests,ps1 file:
Describe "Invoke-Item tests on Windows" -Tags "CI","RequireAdminOnWindows" {

@iSazonov iSazonov added the CL-General

Indicates that a PR should be marked as a general cmdlet change in the Change Log

label

Oct 6, 2020

@SteveL-MSFT

@iSazonov the problem is if anyone updates the code and uses CheckIfConsoleApplication() it will have a similar problem as IsWindowsApplication doesn't get updated if we just do the change you are suggesting.

As for the test, it appears the hang only occurs if the associated process explicitly supports elevation like mmc.exe. I can add a test that runs if diskmgmt.msc and mmc.exe are on the system, otherwise it'll be skipped. At least it can work locally.

@iSazonov

the problem is if anyone updates the code and uses CheckIfConsoleApplication() it will have a similar problem as IsWindowsApplication doesn't get updated if we just do the change you are suggesting.

My thoughts is that switching to mmc.exe is temporary and we should follow diskmgmt.msc. Otherwise it will very tricky code - input is diskmgmt.msc but result is for mmc.exe.
And I'd remove IsWindowsApplication and IsConsoleApplication because they is used once.

@SteveL-MSFT

@iSazonov but diskmgmt.msc is not the process so following it doesn't make sense unless I'm misunderstanding. Removing IsWindowsApplication and IsConsoleApplication makes sense.

@iSazonov

If we remove IsWindowsApplication we remove _isWindowsApplication and this resolves our discussion (til we will want to make IsWindowsApplication public :-) )

iSazonov

@@ -423,7 +400,8 @@ private void InitNativeProcess()

_startPosition = new Host.Coordinates();

CalculateIORedirection(out redirectOutput, out redirectError, out redirectInput);
bool isWindowsApplication = IsWindowsApplication(this.Path);

Choose a reason for hiding this comment

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

These names are so similar that it can be confused. Can we keep the old name for the method?

Choose a reason for hiding this comment

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

The old name with the Check makes it slightly harder to understand reading the code as it's not as clear what true/false means with Check vs Is. The alternative is to have the local variable be isConsoleApplication to differentiate I guess.

Choose a reason for hiding this comment

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

We could use bool windowsApplication without Bulgarian prefix.
If no I agreed with isWindowsApplication.

Choose a reason for hiding this comment

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

Since this code currently is only used in one place, I think the casing diff is ok and prefer the is making it a bit more readable

@iSazonov

As unrelated notice. The code fallback to default shell (cmd.exe/bash) to invoke an item. But what if we assign PowerShell as default shell? Infinite cycle?
It seems we need to review all places where we fallback to default shell to understand the consequences.

@SteveL-MSFT

@iSazonov can you point me to what code you are referring to?

@iSazonov

In the context - line 488. If UseShellExecute is true we call PowerShell again?

@SteveL-MSFT

@iSazonov that line with UseShellExecute only starts a new process if the previous attempt fails because it's not an executable and isn't associated with an executable so it tries to let the Shell try to start it. I don't see the problem.

@iSazonov

@SteveL-MSFT

@iSazonov ok, now I see what you mean. However, on my macBook I can see that Process.Start() doesn't start a new shell to run the exe.

iSazonov

@iSazonov

I'd expect the cycle with:

  1. Set PowerShell as default shell
  2. Run PowerShell unelevated
  3. Run diskmgmt.msc

In line 534 we will have UseShellExecute = true and run PowerShell again.

@SteveL-MSFT

@iSazonov perhaps you can open a new issue to discuss that specific concern, I don't see additional pwsh processes, however

@iSazonov

@SteveL-MSFT I looked at the UseShellExecute implementation. It does not use a call to default shell. On Windows it uses ShellExecuteEx P/Invoke, on Unix it uses x-bit. So the cycle would be only if PowerShell associated with the extension or file.
On Windows this is an incredible scenario. On Unix the code path is blocked in line 494.
On Unix I still had a concern because in line 488 UseShellExecute is false and Process.Start will just fork and execute - if the file has #!pwsh in header the pwsh would run again but I tried on WSL without problems. So the question is closed.

@ghost

🎉v7.1.0-rc.2 has been released which incorporates this pull request.:tada:

Handy links:

@ghost ghost mentioned this pull request

Oct 21, 2020

@ghost

🎉v7.2.0-preview.1 has been released which incorporates this pull request.:tada:

Handy links:

@God-damnit-all

Is this in 7.1 stable? I don't see a revert to this commit in the diff log between 7.1 and 7.1-rc2, but it's re-incorporated in 7.2 preview 1.

@iSazonov

It was included in v7.2.0-preview.1. You can check that 7.1 works well in the scenario.

@God-damnit-all

It was included in v7.2.0-preview.1. You can check that 7.1 works well in the scenario.

So... both versions have it?

@iSazonov