Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Add Jumplist 'Run as Administrator' to Taskbar on Windows#6913

Merged
TravisEz13 merged 13 commits intoPowerShell:masterfrom
bergmeister:JumpListStartAsAdmin_COM
May 31, 2018
Merged

Add Jumplist 'Run as Administrator' to Taskbar on Windows#6913
TravisEz13 merged 13 commits intoPowerShell:masterfrom
bergmeister:JumpListStartAsAdmin_COM

Conversation

@bergmeister
Copy link
Contributor

@bergmeister bergmeister commented May 21, 2018

PR Summary

travisez13: marking as WIP while I run compliance tools to make sure this doesn't break any compliance rules. Don't remove WIP until I've signed off on compliance issues

Closes #6649

This is a port of existing C++ Windows PowerShell code from MainEntry.cpp
Some of the code has been copied and minified from the WindowsApiPack.

The code is not compiled for Linux (not sure also another condition is needed for Windows on ARM?).
The code checks if the PowerShell process has a window handle by checking the startupinfo and only then tries to populate the list (and also checks if there is a slot available in the jumplist).
Tested on Windows 10 1803, jumpLists have been supported in Windows since Windows 7, which matches what PowerShell Core supports.

This is how it looks like:

image

Note that I found that on WinServer 2012 R2, the icon does not recognised and is empty instead, I tried using the SetIconLocation method to point it to the ico file instead (which works on Win10) but it did not work on WinServer2012R2, therefore I suspect I have either an odd VM or it is an OS bug. Since this is only a minor optical and non-functional detail on a less common OS, I think this is OK.

PR Checklist

@TravisEz13 TravisEz13 changed the title WIP [feature] Add Jumplist 'Run as Administrator' to Taskbar on Windows WIP Add Jumplist 'Run as Administrator' to Taskbar on Windows May 21, 2018
@TravisEz13
Copy link
Member

[feature] only works for commits

<DefineConstants>$(DefineConstants);CORECLR</DefineConstants>
</PropertyGroup>

<ItemGroup Condition=" '$(Configuration)' == 'Linux' ">
Copy link
Member

Choose a reason for hiding this comment

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

Configuration Linux has been removed. See Common Props for how to conditionally remove for Non-Windows.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thanks, I addressed that.

@bergmeister bergmeister changed the title WIP Add Jumplist 'Run as Administrator' to Taskbar on Windows Add Jumplist 'Run as Administrator' to Taskbar on Windows May 21, 2018
Copy link
Member

@TravisEz13 TravisEz13 left a comment

Choose a reason for hiding this comment

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

I reviewed, but since this a WIP... I'm just marking my place.

@bergmeister
Copy link
Contributor Author

bergmeister commented May 21, 2018

@TravisEz13 Thanks for having a quick look and the helpful comments. I finished the last cleanup items and it is now not WIP any more. I manually tested it already on a clean VM with one of the early AppVeyor build artifacts.

@TravisEz13 TravisEz13 self-assigned this May 21, 2018
@TravisEz13
Copy link
Member

@bergmeister Would performance be better if you checked if we already are registered and don't register?

Also, I need to run compliance tools due to some changes you made. I'm going to mark this as WIP again until the compliance tools say this change is good.

@TravisEz13 TravisEz13 changed the title Add Jumplist 'Run as Administrator' to Taskbar on Windows WIP<travisez13>:Add Jumplist 'Run as Administrator' to Taskbar on Windows May 21, 2018
@bergmeister
Copy link
Contributor Author

@TravisEz13 By 'registered', do you mean if the jumplist has already been populated? As far as I am aware the Windows APIs for the jumplist, do not have 'Get' methods to check their current state (but I might be wrong). Any pointers are welcome.
There is no hurry for the review although getting it into the next preview might be good for testing exposure. So take your time, I will be on a conference from Wednesday on anyway.

@TravisEz13 TravisEz13 changed the title WIP<travisez13>:Add Jumplist 'Run as Administrator' to Taskbar on Windows Add Jumplist 'Run as Administrator' to Taskbar on Windows May 22, 2018
@TravisEz13
Copy link
Member

TravisEz13 commented May 22, 2018

@bergmeister No compliance issue with your new code

As far as I am aware the Windows APIs for the jumplist, do not have 'Get' methods to check their current state

That answered my question.

@TravisEz13
Copy link
Member

restarted macOS due to #6915

{
if (hResult < 0)
{
throw new Exception($"HResult from COM call was negative, which indicates a failure. Was: '{hResult}'");
Copy link
Collaborator

Choose a reason for hiding this comment

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

We use explicit formating in our code and never use the operator $().

Is the message is user-faced? If so we should move it in resource file.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good point. I was thinking about this more after I opened the PR. Initially I wrote it as a check to make sure all COM calls were successful at each stage during development. Looking at it again now, it is probably better to not throw but instead make the function return/stop prematurely (which should not be a problem because we haven't commited the transaction)

private uint Run(CommandLineParameterParser cpp, bool isPrestartWarned)
{
Dbg.Assert(null != cpp, "CommandLine parameter parser cannot be null.");
Dbg.Assert(cpp != null, "CommandLine parameter parser cannot be null.");
Copy link
Collaborator

Choose a reason for hiding this comment

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

Should we replace the assert with throw?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

No strong opinion on this one, this line was only changed because the code style checker flagged it up that null was on the left hand side (which is only necessary in C/C++ but not C#)

@bergmeister
Copy link
Contributor Author

bergmeister commented May 22, 2018

@TravisEz13 I had a more detailed look and the WPF implementation seems to have a 'get' method: https://msdn.microsoft.com/en-us/library/system.windows.shell.jumplist.getjumplist(v=vs.110).aspx
I was mostly reading the WindowsApiPack code and the IShellLink Windows Api docs. I would need to look at the decompiled WPF code to see if there is potential for optimisation (which would include/require at least setting the appId in order to persist the jumplist) but the implementation in Windows PowerShell does not perform such a check.
The WPF implementation seems to cache it:
image
image
image

In general I should also note that I managed to port a subset of the WindowsApiPack to .Net Core and also have a prototype to populate an elevated jumplist with that, which has slightly more advanced techniques (synclock, UI permission checks, etc.) but it would also require bringing in 100 times more code, hence why this PR aims only for a minimal port of the existing Windows PowerShell code, which should be at least better performance wise.

…instead of throwing an exception. Since this now not a user facing string any more, I do not think it is worth having the StringUtil.Format wrapper for it
@iSazonov
Copy link
Collaborator

Does that slow down the PowerShell Core start?

@bergmeister
Copy link
Contributor Author

bergmeister commented May 23, 2018

I have not measured it but I could not feel a difference. It also only affects pwsh on Windows when it is being opened in an actual window.

@iSazonov
Copy link
Collaborator

I guess we get this easily with .Net Core 3.0 - right?

@bergmeister
Copy link
Contributor Author

bergmeister commented May 23, 2018

Maybe but even now I think WPF does not offer the APIs needed for elevation. .Net Core 3 development has only just started and realistically speaking, the chance that they will be delayed is high and they are probably going to deliver a MVP first. But it is not a reason against this PR because one can just delete the folder that I added, so there is no deep integration that would make it difficult to take out in the future

@iSazonov
Copy link
Collaborator

iSazonov commented May 23, 2018

.Net Framework has System.Windows.Shell.JumpList (STA only)

@bergmeister
Copy link
Contributor Author

bergmeister commented May 23, 2018

Yes, but the classes in full .net do not support elevated JumpList entries.

@TravisEz13
Copy link
Member

@SteveL-MSFT Do we want to measure perf before accepting this?

@bergmeister
Copy link
Contributor Author

I could give you a measurement using the Stopwatch class when being compiled in Release and CrossGen'd to give you a rough number. Hardware would be either a 7th Gen i7 Notebook processor or an Azure VM size of your choice.
If you are worried, then we could potentially also implement the option of disabling the feature if a special environment variable is set. Otherwise it is a case of 'there is no such thing as free lunch'.

@iSazonov
Copy link
Collaborator

The code remove one extra mouse click in GUI. This seems to be a very expensive solution, given that there are many non-interactive scenarios.

@bergmeister
Copy link
Contributor Author

Expensive in terms of what? In non-interactive scenarios, the only overhead is one call to get the startupinfo and then the algorithm will not proceed.

@iSazonov
Copy link
Collaborator

Expensive in terms of what?

Over 600 code lines to add one menu item.

@bergmeister
Copy link
Contributor Author

bergmeister commented May 25, 2018

The main code is just the 90 lines in the TaskbarJumpList file, which was a port of the existing Windows PowerShell C++ code in the referenced MainEntry.cpp
The other classes were taken from the WindowsApiPack and already minimized to include only the smallest set of required classes and methods.
Is the concern more about the review or maintenance/support of this PR?

@iSazonov
Copy link
Collaborator

@bergmeister I'm trying to understand the need for this change, the difficulty in supporting and the negative impact on the start. So far I can take it but I don't see much benefits.

@bergmeister
Copy link
Contributor Author

bergmeister commented May 25, 2018

The benefit of this feature is that it halves the time/clicks to open an elevated pwsh shell, brings consistency with Windows PowerShell in terms of user experience and having one less click to do reduces muscle strain in the long term. This issue got opened by a community member in the first place and other people are getting excited about this as well: https://twitter.com/CBergmeister/status/996873883268648960?s=19

The difficulty in writing/supporting this feature is the lack of support for this scenario from the Windows/.Net side:
The COM APIs are not very user friendly and require memory structs that are awkward to work with in .Net (especially PropVariant). Therefore the Windows team made the WindowsApiPack in .Net 3.5 and stopped supporting it because it got merged into WPF in .Net 4, but both implementations are missing overloads to support elevation and do not provide access to the low level shell object pointer)
Hence why I ripped out the minimum necessary code for us and added a call to the COM Api that enables elevation. But the whole algorithm is the same that Windows PowerShell uses as well.

I have not measured the exact performance impact yet but I think it is minimal and negligible.

@bergmeister
Copy link
Contributor Author

bergmeister commented May 26, 2018

@TravisEz13 @iSazonov @SteveL-MSFT I measured the performance impact using the StopWatch class when compiling locally with -CrossGen and Release configuration: The first time it took 70 ms and after that the times were around 55 ms, measurements fluctuated around 5-10 ms. When pwsh is launched inside an existing shell (or is non-interactive), then it is less than 1ms with the new commit.

Is this OK performance wise since the performance impact only happens when a JumpList can be provided? Otherwise we would have to do something like kicking the work off in a background thread. I am not an expert on COM/Interop performance but I think there is not much potential for optimisation. When the PowerShell host moves back to STA I would expect a performance increase though.

  • OS: Windows 10 1803 (10.0.17134 Build 17134)
  • CPU: Intel i7-7700HQ (4 core Laptop version with 45 W TDP)
  • Disk: SSD - Samsung EVO 850

…ow (and exit even earlier when being in a non-interactive thread)

This reduces the overhead to less than 1ms when the shell is not owning the current window
GetStartupInfo(out StartUpInfo startupInfo);
var STARTF_TITLEISLINKNAME = 0x00000800;
if (startupInfo.lpTitle == null || (startupInfo.dwFlags & STARTF_TITLEISLINKNAME) != STARTF_TITLEISLINKNAME)
if (Environment.UserInteractive)
Copy link
Collaborator

Choose a reason for hiding this comment

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

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Lol. I will remove it then. The idea behind this was to not even have a single COM call in non-interactive mode (but it will should stop after the call to getstartupinfo then)

Copy link
Collaborator

Choose a reason for hiding this comment

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

I think it should be PowerShell internal flag - whether it interacts with an user.

…urns true). In non-interactive mode the algorithm will stop after the call to getstartupinfo then
@TravisEz13 TravisEz13 merged commit b851b55 into PowerShell:master May 31, 2018
@iSazonov
Copy link
Collaborator

iSazonov commented Jun 1, 2018

@bergmeister Thanks for your contribution! I am delighted that you have found this solution.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

PowerShell Core for Windows - add the "Run as Administrator" action to taskbar button.

3 participants