Skip to content
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

KozaniHostRuntime implementation and FTA and protocol launch support #3714

Merged
merged 15 commits into from
Aug 4, 2023

Conversation

rhuang-msft
Copy link
Contributor

@rhuang-msft rhuang-msft commented Jul 9, 2023

KozaniHostRuntime

**Added logics to get remote app AUMID from app extension of the Local Variant Package (LVP).

Added logics to get the configuration file paths (connection.rdp file and AdditionalSettings.txt) from LVP's app data folder.

Implemented IKozaniStatusCallback interface to handle remote app status changes. It will exit the KozaniHostRuntime process if the remote app is terminated, or the remote app activation fails, or the activation does not start a new instance (like UWPs that do not start a new app process for multiple activations).

KozaniManager

Fixed various bugs in process lifetime tracking, race condition, etc.

Moved Remote Desktop Client (RDC) launcher code to KozaniManagerRuntime.dll, which is loaded in-proc by the KozaniHostRuntime.exe to ensure RDC is launched in foreground. KozaniManager.exe is a background process with no UIs. Launching RDC by it will not guarantee the RDC login window to show up in foreground. Instead, it will be launched minimized with flashing icon at the taskbar, unless RDC itself pops up a warning dialog in the first connection to the new host. KozaniHostRuntime.exe on the other hand, has foreground privileges as it is launched by the user by activating the LVP app. Launching RDC from KozaniHostRuntime.exe will guarantee the RDC login dialog shows up in foreground.

KozaniRemoteManager

Added support for FTA and protocol launch in remote desktop server side.

Moved activation code to KozaniRemoteManagerLauncher.exe to ensure the activated apps show up in foreground. KozaniRemoteManager.exe is a background process with no UIs. Launching apps from it will not guarantee the app will have foreground. If the app activation causes a new process to be created, then the windows from the new process will show up in foreground. However, if the activation does not create a new process, then the app window will not show up in foreground. UWP apps are single instancing and once launched, subsequent activation will not create a new process. It will be problematic if the activation is done from KozaniRemoteManager.exe as if the UWP app is already running and not in foreground (e.g., minimized), the activation will not bring the app to foreground, like nothing has happened. Win32 apps have the same problem if they implement single instancing. KozaniRemoteManagerLauncher.exe on the other hand, has foreground privileges as it is launched by remote desktop shell by RAIL. It is also a new process each time it is launched by the client. Activating apps from it will guarantee the apps will show up in foreground, whether a new process is created or not.

Used IApplicationActivationManager::ActivateForFile for UWP FTA activation, IApplicationActivationManager::ActivateForProtocol for UWP protocol activation because these 2 APIs only works for UWPs. Used ShellExecuteEx for Packaged desktop apps FTA and protocol activation. Implemented registry lookup logics to find the ProgId of the FTA and protocol association for the app to be used in ShellExecuteEx.

rhuang-msft and others added 5 commits May 3, 2023 17:18
Co-authored-by: William Cheng <wcheng@ntdev.microsoft.com>
1. Enabled RDC launch to the foreground.
2. Hook up wil logging to trace providers
3. Added KozaniHostRuntime to KozaniManager MSIX package.
@rhuang-msft
Copy link
Contributor Author

/azp run

@azure-pipelines
Copy link

Azure Pipelines could not run because the pipeline triggers exclude this branch/path.

Copy link
Member

@DrusTheAxe DrusTheAxe left a comment

Choose a reason for hiding this comment

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

However, if it is activated by file type association (FTA) or protocol, the command line will not contain the parameter. That's why we need additional logics to parse the manifest xml to get that info for FTA and protocol activation

Can't you query the ActivatedEventArgs to determine why KozaniHostRuntime is being activated (e.g. ActivationKind.FileTypeAssociation)?

https://learn.microsoft.com/en-us/windows/apps/desktop/modernize/get-activation-info-for-packaged-apps

or even better https://learn.microsoft.com/en-us/windows/windows-app-sdk/api/winrt/microsoft.windows.applifecycle.appinstance.getactivatedeventargs?view=windows-app-sdk-1.3

(Any games involving "Find, Load and Parse appxmanifest.xml" is strongly discouraged for the yuuuge perf implications)

@rhuang-msft
Copy link
Contributor Author

@DrusTheAxe , the problem is not because I can't get why KozaniHostRuntime is being activated. Of course we can get that info easily from ActivatedEventArgs. The problem is what the AUMID is for the remote app, associated with the LVP app being activated. It is encoded in the manifest of the LVP, like this:

The uap10:Parameters will be passed to the KozaniHostRuntime.exe as command line parameter IF the LVP app is activated by launch contract (i.e., clicking the tile to launch it). However, if it is activated by FTA or Protocol, then the command line parameter will NOT contain the info. Instead, it will be the file path for FTA, or uri for protocol launch. That's why we need to load up the manifest to get the remote app AUMID. LVP's manifest is very small and perf hit is minimal.

@DrusTheAxe
Copy link
Member

DrusTheAxe commented Jul 11, 2023

LVP's manifest is very small and perf hit is minimal

That's only true in the trivial case. The LVP isn't necessarily trivial or even generated from the remote package's manifest. There are those larger scenarios we've discussed where it gets more interesting (and complex).

The problem is what the AUMID is for the remote app, associated with the LVP app being activated. It is encoded in the manifest of the LVP, like this:

Ahhh. So when the LVP app runs you need to know the associated app on the server to run?

RECOMMENDATION: Use appextension.

Add to the LVP's manifest

<uap3:Extension Category="windows.appExtension">
    <uap3:AppExtension Name="com.microsoft.kozani.localvariant.remoteapp.AUMID" Id="kozani.remoteapp.PRAID" DisplayName="Kozani RemoteApp REMOTEAUMID">
        <uap3:Properties>
            <RemoteApp AppUserModelId="REMOTEAUMID"/>
        </>
    </>
</>

where PRAID=the parent <Application>'s PRAID, AUMID=the parent <Application>'s AUMID and REMOTEAUMID=the remoteapp's AUMID. Then KozaniHostRuntime.exe can use the AppExtension API to retrieve this e.g.

aumid = GetCurrentApplicationUserModelId()
pkgfamilyname,praid = ParseApplicationUserModelId(aumid)
catalog = AppExtensionCatalog.Open("com.microsoft.kozani.localvariant.remoteapp." + aumid)
list<AppExtension> aes = co_await catalog.FindAllAsync()
IPropertySet properties = co_await aes[0].GetExtensionPropertiesAsync()
remoteaumid = properties.Lookup("RemoteApp").Lookup("@AppUserModelId")

The manifest is parsed when the app's installed and AppExtension is the generic metadata mechanism with APIs working off that already parsed and optimized data. Leverage that.

There are things we can do in the longer term to further optimize this in both code (Kozani, Activation and other Windows components) and data (e.g. appxmanifest.xml). Short-term this is more in line with other like efforts and while still a data contract it's slightly less of one, and more so an unambiguous one we can more readily migrate in the future.

"Find, Load and Parse appxmanifest.xml" is strongly discouraged.

@rhuang-msft
Copy link
Contributor Author

@DrusTheAxe , I have updated the PR with the use of app extension. Please review. Thx.

@rhuang-msft
Copy link
Contributor Author

rhuang-msft commented Jul 20, 2023 via email

dev/Kozani/KozaniHostRuntime/main.cpp Outdated Show resolved Hide resolved
dev/Kozani/KozaniHostRuntime/main.cpp Outdated Show resolved Hide resolved
dev/Kozani/KozaniHostRuntime/main.cpp Outdated Show resolved Hide resolved
dev/Kozani/KozaniHostRuntime/main.cpp Outdated Show resolved Hide resolved
dev/Kozani/KozaniHostRuntime/main.cpp Show resolved Hide resolved
Copy link
Member

@DrusTheAxe DrusTheAxe left a comment

Choose a reason for hiding this comment

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

Nicely done. Only 2 Qs come to mind:

#1 Manifest extension XML
Is that doc'd? I see the comment in dev/Kozani/KozaniHostRuntime/main.cpp line 118+ which looks close (see comments there). Is that meant to doc the expected info in the manifest?

#2 E2E testing
Is there an automated E2E test for this here? Or in the Aggregator as an Integration test? Is that not possible without a remote environment?

@rhuang-msft
Copy link
Contributor Author

Nicely done. Only 2 Qs come to mind:

#1 Manifest extension XML Is that doc'd? I see the comment in dev/Kozani/KozaniHostRuntime/main.cpp line 118+ which looks close (see comments there). Is that meant to doc the expected info in the manifest?

The doc is not created yet. Right now the comments in line 118 serves the documentation.

#2 E2E testing Is there an automated E2E test for this here? Or in the Aggregator as an Integration test? Is that not possible without a remote environment?
No automatic E2E testing as of now. Yes, it needs a remove environment for E2E.

@rhuang-msft
Copy link
Contributor Author

/azp run

@azure-pipelines
Copy link

Azure Pipelines could not run because the pipeline triggers exclude this branch/path.

@rhuang-msft
Copy link
Contributor Author

/azp run

@azure-pipelines
Copy link

Azure Pipelines could not run because the pipeline triggers exclude this branch/path.

@rhuang-msft rhuang-msft merged commit d82f92c into develop Aug 4, 2023
2 checks passed
@rhuang-msft rhuang-msft deleted the user/rhuang-msft/KozaniHostRuntime1 branch August 4, 2023 05:24
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.

2 participants