Open conioh opened 2 years ago
@DrusTheAxe Are runas
scenarios supported? I can repro this and a similar issue (inability to find machine-wide runtime).
@DrusTheAxe Are
runas
scenarios supported? I can repro this and a similar issue (inability to find machine-wide runtime).
Note that I have installed the runtime for the other user too.
Hmmm. I haven't seen the Run as another user feature before. Let me do some investigation to determine what it's actually doing.
As a general rule, you can run an app using WinAppSDK as long as the runtime is installed for that user e.g. the Microsoft.WindowsAppRuntime.1.1 MSIX package is registered for that user.
Alternatively, if the package is provisioned then it's made available to all users. At login a list of packages you should have but don't (and have but shouldn't) is built and processed, before you get to the desktop.
I doubt Run as another user is performing a full login. If it's just CreateProcessAsUser then results would depend on state of the machine and that user's profile. Will do some digging.
Hmmm. I haven't seen the Run as another user feature before. Let me do some investigation to determine what it's actually doing.
That's odd. It's been there at least since Windows XP, where it was called Run as..., and probably even before that, but my memory doesn't go that far.
As a general rule, you can run an app using WinAppSDK as long as the runtime is installed for that user e.g. the Microsoft.WindowsAppRuntime.1.1 MSIX package is registered for that user.
Right. Except it doesn't. Hence this issue.
Alternatively, if the package is provisioned then it's made available to all users. At login a list of packages you should have but don't (and have but shouldn't) is built and processed, before you get to the desktop.
That's interesting but not the situation here, since I've run the runtime installer from the second user, and when the TS session belongs to the second user the test program runs.
I doubt Run as another user is performing a full login. If it's just CreateProcessAsUser then results would depend on state of the machine and that user's profile. Will do some digging.
I don't know what that means. A call to CreateProcessAsUser
requires a previous call to LogonUser
or equivalent, and the flags passed to it are certainly not LOGON32_LOGON_BATCH
or LOGON32_LOGON_SERVICE
so that pretty much leaves only LOGON32_LOGON_INTERACTIVE
. What's a "full login"?
Anyway, again, this doesn't really matter here since, again, I have installed the runtime for the second user, being logged on to the console as the second user, so there was no dependency on the provisioned package being auto-installed for the user.
I have updated the steps to reproduce above in hopes of making the situation more clear.
Is it relevant that this happens on a terminal session? I can see it crash when trying to run an unpackaged app from a normal desktop session as different user. It also crashes when trying to run as admin which is supposed to work with 1.1 onwards. So what is going on here?
Is it relevant that this happens on a terminal session? I can see it crash when trying to run an unpackaged app from a normal desktop session as different user. It also crashes when trying to run as admin which is supposed to work with 1.1 onwards. So what is going on here?
That's just the term. I never said it was a remote session.
In the context of users and programs run by those users there are two things called "a session".
One of them is logon sessions, which represent a login for the purposes of permissions (including by a service account, or a login for SMB), are the basis for tokens, are represented by the SEP_LOGON_SESSION_REFERENCES
struct and can be enumerated with LsaEnumerateLogonSessions
.
The other one is Terminal Services sessions which represent a physical connection a console†, local or remote, used by Remote Desktop Services and Fast User Switching. They can be enumerated by the WTSEnumerateSessions
function, where WTS stands for Windows Terminal Services. Certain information about them is published via the WM_WTSSESSION_CHANGE
windows message, where again WTS stands for the same thing.
Each token is linked to a specific Terminal Services session, and while the member of the TOKEN
struct that identifies the session is called simply SessionId
rather than the lengthy MicrosoftWindowsTerminalServicesSessionId
(as is the member of the MM_SESSION_SPACE
struct etc.), WinDbg's !token
command does use the term "TS session". Once more "TS" stands for Terminal Services.
0: kd> !token ffffd50c6602a060
_TOKEN 0xffffd50c6602a060
TS Session ID: 0
User: S-1-5-18
User Groups:
00 S-1-16-16384
Attributes - GroupIntegrity GroupIntegrityEnabled
...
0: kd> dt nt!_TOKEN ffffd50c6602a060
+0x000 TokenSource : _TOKEN_SOURCE
+0x010 TokenId : _LUID
+0x018 AuthenticationId : _LUID
+0x020 ParentTokenId : _LUID
+0x028 ExpirationTime : _LARGE_INTEGER 0x06207526`b64ceb90
+0x030 TokenLock : 0xffff8887`f1245210 _ERESOURCE
+0x038 ModifiedId : _LUID
+0x040 Privileges : _SEP_TOKEN_PRIVILEGES
+0x058 AuditPolicy : _SEP_AUDIT_POLICY
+0x078 SessionId : 0
...
0: kd> !token ffffd50c75adb7f0
_TOKEN 0xffffd50c75adb7f0
TS Session ID: 0x1
User: S-1-5-21-3728847233-3017425221-756437700-1001
User Groups:
00 S-1-16-0
Attributes - GroupIntegrity GroupIntegrityEnabled
...
0: kd> dt nt!_TOKEN ffffd50c75adb7f0
+0x000 TokenSource : _TOKEN_SOURCE
+0x010 TokenId : _LUID
+0x018 AuthenticationId : _LUID
+0x020 ParentTokenId : _LUID
+0x028 ExpirationTime : _LARGE_INTEGER 0x7fffff36`d5969fff
+0x030 TokenLock : 0xffff8887`f817be90 _ERESOURCE
+0x038 ModifiedId : _LUID
+0x040 Privileges : _SEP_TOKEN_PRIVILEGES
+0x058 AuditPolicy : _SEP_AUDIT_POLICY
+0x078 SessionId : 1
...
This term is used throughout Windows.
Here, since the processes run under different users, using different logon sessions, but in the same TS session, it was appropriate to qualify the term so as to reduce confusion. "Two processes in different logon session running in the same session" is undoubtedly worse than "two processes in different logon sessions running in the same Terminal Services session."
"Normal desktop session" isn't a thing. It's unclear and suggests the question are there abnormal desktop sessions. There's an existing term and I simply used it.
It also crashes when trying to run as admin which is supposed to work with 1.1 onwards. So what is going on here?
Programming is hard and not everyone is equally qualified or able to do it adequately.
† Except in the special case of session 0 since Windows Vista. Vista leverages the security boundary formed by TS session (in the Object Manager etc.) to separate certain system processes from user processes and protect them. That Windows XP gave the first user that logs on to the machine access to the "global namespace" it just odd and that was fixed in Vista.
In my case on a normal dekstop session, with no terminal services involved, event viewer logs the following error:
<EventData>
<Data>Windows App Runtime</Data>
<Data>ERROR 0x80070520: Bootstrapper initialization failed while looking for version 1.1 (MSIX package version >= 1002.543.1943.0)</Data>
</EventData>
Further investigation shows that adding <WindowsAppSDKSelfContained>true</WindowsAppSDKSelfContained>
to the project or publishing file lets the application run successfully with a different than the logged on user.
Install the Windows App SDK Runtime version 1.1.0 x64
Does this repro with a recent servicing update (1.1.2+)? There were some fixes in 1.1.2 that may be significant here.
Is 1.1.2+ registered for the other user? Easiest way to tell is via an admin cmd prompt
powershell -c get-appxpackage -AllUsers micro*win*apprun*
and the PackageUserInformation property shows which users have the package registered e.g.
...
PackageUserInformation : {S-1-5-21-4071067175-473297690-1648030122-1003 [bob]: Installed}
...
Does this repro with a recent servicing update (1.1.2+)? There were some fixes in 1.1.2 that may be significant here.
As expected, the same crash still happens with:
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Microsoft.Windows.CppWinRT" version="2.0.210806.1" targetFramework="native" />
<package id="Microsoft.Windows.ImplementationLibrary" version="1.0.211019.2" targetFramework="native" />
<package id="Microsoft.Windows.SDK.BuildTools" version="10.0.22000.194" targetFramework="native" />
<package id="Microsoft.WindowsAppSDK" version="1.1.2" targetFramework="native" />
</packages>
or (updated):
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Microsoft.Windows.CppWinRT" version="2.0.220608.4" targetFramework="native" />
<package id="Microsoft.Windows.ImplementationLibrary" version="1.0.220201.1" targetFramework="native" />
<package id="Microsoft.Windows.SDK.BuildTools" version="10.0.22621.1" targetFramework="native" />
<package id="Microsoft.WindowsAppSDK" version="1.1.3" targetFramework="native" />
</packages>
Runtime packages:
PS C:\> Get-AppxPackage -AllUsers Micro*Win*AppRun* | Select-Object PackageFullName, PackageUserInformation | fl
PackageFullName : Microsoft.WindowsAppRuntime.1.1_1003.565.600.0_x64__8wekyb3d8bbwe
PackageUserInformation : {S-1-5-21-RIDs-1002 [SomeUsername]: Installed,
S-1-5-21-RIDs-1001 [SomeUsernameA]: Installed}
PackageFullName : Microsoft.WindowsAppRuntime.1.1_1003.565.600.0_x86__8wekyb3d8bbwe
PackageUserInformation : {S-1-5-21-RIDs-1002 [SomeUsername]: Installed,
S-1-5-21-RIDs-1001 [SomeUsernameA]: Installed}
PackageFullName : MicrosoftCorporationII.WinAppRuntime.Main.1.1_1003.565.600.0_x64__8wekyb3d8bbwe
PackageUserInformation : {S-1-5-21-RIDs-1002 [SomeUsername]: Installed,
S-1-5-21-RIDs-1001 [SomeUsernameA]: Installed}
PackageFullName : MicrosoftCorporationII.WinAppRuntime.Singleton_1003.565.600.0_x64__8wekyb3d8bbwe
PackageUserInformation : {S-1-5-21-RIDs-1002 [SomeUsername]: Installed,
S-1-5-21-RIDs-1001 [SomeUsernameA]: Installed}
PackageFullName : Microsoft.WinAppRuntime.DDLM.1003.565.600.0-x6_1003.565.600.0_x64__8wekyb3d8bbwe
PackageUserInformation : {S-1-5-21-RIDs-1002 [SomeUsername]: Installed,
S-1-5-21-RIDs-1001 [SomeUsernameA]: Installed}
PackageFullName : Microsoft.WinAppRuntime.DDLM.1003.565.600.0-x8_1003.565.600.0_x86__8wekyb3d8bbwe
PackageUserInformation : {S-1-5-21-RIDs-1002 [SomeUsername]: Installed,
S-1-5-21-RIDs-1001 [SomeUsernameA]: Installed}
CreateLifetimeManagerViaEnumeration
make the following call:
with the following values:
lifetimeManagerApplicationUserModelId = wchar_t [130] "Microsoft.WinAppRuntime.DDLM.1003.565.600.0-x6_8wekyb3d8bbwe!DDLM"
packageFamilyName = "Microsoft.WinAppRuntime.DDLM.1003.565.600.0-x6_8wekyb3d8bbwe"
packageFullName = "Microsoft.WinAppRuntime.DDLM.1003.565.600.0-x6_1003.565.600.0_x64__8wekyb3d8bbwe"
The package indeed exists:
PS C:\> Get-AppxPackage | Where-Object -Property "PackageFullName" -EQ "Microsoft.WinAppRuntime.DDLM.1003.565.600.0-x6_1003.565.600.0_x64__8wekyb3d8bbwe" | Select-Object Name, PackageFamilyName, PackageFullName | fl
Name : Microsoft.WinAppRuntime.DDLM.1003.565.600.0-x6
PackageFamilyName : Microsoft.WinAppRuntime.DDLM.1003.565.600.0-x6_8wekyb3d8bbwe
PackageFullName : Microsoft.WinAppRuntime.DDLM.1003.565.600.0-x6_1003.565.600.0_x64__8wekyb3d8bbwe
And has the requested AUMID. (I'll skip dumping the manifest here.)
From there the call flow is the following:
Microsoft_WindowsAppRuntime_Bootstrap!CreateLifetimeManagerViaEnumeration;
|-- twinui_appcore!ApplicationActivationManagerProxy::ActivateApplication;
|-- daxexec!TryActivateDesktopAppXApplication;
|-- daxexec!EvaluateApplicationLaunch;
|-- daxexec!GetActivationProperties;
|-- daxexec!GetTargetUserTokenAndUserContext;
|-- daxexec!wil::open_current_access_token;
|-- usermgrcli!UMgrQueryUserContext;
daxexec!wil::open_current_access_token
simply returns the process token and then usermgrcli!UMgrQueryUserContext
does an RPC call that fails with error 0x80070520.
I don't see anything that was done in relation to this, which is why it makes sense that except for the version (the AUMID passed to aam->ActivateApplication
) this is exactly the same flow and error as with the previous versions.
Regarding your previous comment:
I doubt Run as another user is performing a full login. If it's just CreateProcessAsUser then results would depend on state of the machine and that user's profile. Will do some digging.
runas.exe
uses CreateProcessWithLogonW
which does login as full as any other and is enough to run any other program I can think of and have tried.
I don't know if the fault is with Windows or the Windows App SDK, and without source code access I can't debug it any more than point the stack trace mentioned above, but even if the problem is with Windows such that everything in Windows works under such a login except for IApplicationActivationManager::ActivateApplication
, maybe the newest application framework shouldn't depend on it?
I don't know if the fault is with Windows or the Windows App SDK
That'll be Windows. From your info it's pretty clear CreateProcessWithLogonW
and app activation aren't intersecting in expected/desired ways, and that's Windows and its support for MSIX.
WinAppSDK provides added functionality, in some cases working around Windows to achieve equivalent ends, but only so much you can work around.
Hunting up the activation experts...
+@jsidewhite @brialmsft @dhoehna
daxexec!wil::open_current_access_token
simply returns the process token and thenusermgrcli!UMgrQueryUserContext
does an RPC call that fails with error 0x80070520.
BTW 0x80070520 == ERROR_NO_SUCH_LOGON_SESSION. "A specified logon session does not exist. It may already have been terminated."
Comparing notes with the activation experts
That'll be Windows. From your info it's pretty clear
CreateProcessWithLogonW
and app activation aren't intersecting in expected/desired ways, and that's Windows and its support for MSIX.
Indeed seems so. The following code fails with the same error when run as a different user (and succeeds as the same user):
...
IApplicationActivationManagerPtr aam{ CLSID_ApplicationActivationManager };
DWORD pid{};
HRESULT hr = aam->ActivateApplication(L"Microsoft.WinDbg_8wekyb3d8bbwe!Microsoft.WinDbg", L"", AO_NOERRORUI | AO_NOSPLASHSCREEN, &pid);
(Same happens with AO_NONE
, I used the same flags as you just to make sure.)
But:
WinAppSDK provides added functionality, in some cases working around Windows to achieve equivalent ends, but only so much you can work around.
Why is it so important to lookup and load the framework using the whole AppX/AAM thing?
What's so wrong with distributing the Windows App SDK runtime as a bunch of DLLs, installed in something like %LOCALAPPDATA%\Microsoft\WindowsAppSDKRuntime\_version_\
?
Even if there's some reason to distribute as a "package" for packaged apps, why not also additionally distribute as just-a-bunch-of-files?
Is there functionality that is available to unpackaged apps that can't be implemented in a unpackaged adaptation of the framework?
Edit: Given that the Windows App SDK officially supports "self-contained apps", and it actually works - both in general and in the runas scenario - I tend to think there is no such functionality. In that case I can hardly see a reason for not providing "framework-dependent apps" (i.e. apps that don't have to carry extra 60MB and use the newest framework available on the host machine) a version of the framework that doesn't rely on the AAM and all the related machinery.
Why is it so important to lookup and load the framework using the whole AppX/AAM thing?
The core problem is the Deployment stack in Windows doesn't know about unpackaged apps (not until Win11 and the new inbox Dynamic Dependencies API). Thus if you install an unpackaged app and use the bootstrapper to give a process access to the framework's content, and Deployment happens to run and decide "hey, this framework is unused because I only know about static dependencies declared in packaged apps' appxmanifest.xml and I see none needing this framework package so let's remove it". Even though your unpackaged process is running using it. Aaaand BadMojo™.
To enable unpackaged apps to use packaged goods we need to make it clear to the Deployment stack for both install-time and run-time.
For install-time we need a main package with a declared dependency on the framework package. As long as that main package is installed the framework won't get removed. WinAppSDK's Main + DDLM packages satisfy that.
For run-time we need a running process with package identity and its appxmanifest.xml to declare a <PackageDependency>
on the framework package. WinAppSDK's DDLM package solves that by declaring (a) a <PackageDependency>
on the framework package and (b) an application. When MddBootstrapInitialize
finds a applicable framework package, it activates the app to create a process meeting a+b (pkgidentity + static dependency on the framework) to hang around until MddBootstrapShutdown
(or process death). This expresses to the Deployment stack (in ways it understands!) regarding our need for the framework package not to be serviced at this time.
Lacking this 'helper' DDLM process we'd don't get unpackaged apps reliably using framework packages.
What's so wrong with distributing the Windows App SDK runtime as a bunch of DLLs
The tradeoffs of MSIX vs redistributables (i.e. SelfContained) are documented but sorry, link eludes my memory at the moment. Servicing, perf and disk footprint are some of the differences.
SelfContained has its merits but it's not without its downsides. Neither option (MSIX vs SelfContained) fully supplants the other which is why we support both :-)
The core problem is the Deployment stack in Windows doesn't know about unpackaged apps (not until Win11 and the new inbox Dynamic Dependencies API). Thus if you install an unpackaged app and use the bootstrapper to give a process access to the framework's content, and Deployment happens to run and decide "hey, this framework is unused because I only know about static dependencies declared in packaged apps' appxmanifest.xml and I see none needing this framework package so let's remove it". Even though your unpackaged process is running using it. Aaaand BadMojo™.
I don't see how that's relevant to unpackaged EXEs loading unpackaged DLLs.
Since the packaging infrastructure isn't aware of unpackaged apps, for an unpackaged app to take a reference of a package it needs to do something special, explicit, like calling TryCreatePackageDependency
and AddPackageDependency
.
That's great.
But why isn't that enough? Why do you call IAAM->ActivateApplication
instead of just LoadLibrary
?
To enable unpackaged apps to use packaged goods we need to make it clear to the Deployment stack for both install-time and run-time.
For install-time we need a main package with a declared dependency on the framework package. As long as that main package is installed the framework won't get removed. WinAppSDK's Main + DDLM packages satisfy that.
For run-time we need a running process with package identity and its appxmanifest.xml to declare a
<PackageDependency>
on the framework package. WinAppSDK's DDLM package solves that by declaring (a) a<PackageDependency>
on the framework package and (b) an application. WhenMddBootstrapInitialize
finds a applicable framework package, it activates the app to create a process meeting a+b (pkgidentity + static dependency on the framework) to hang around untilMddBootstrapShutdown
(or process death). This expresses to the Deployment stack (in ways it understands!) regarding our need for the framework package not to be serviced at this time.Lacking this 'helper' DDLM process we'd don't get unpackaged apps reliably using framework packages.
Again, this doesn't answer the question and talks about something comepletely different.
To enable unpackaged apps to use packaged goods you need to
The tradeoffs of MSIX vs redistributables (i.e. SelfContained) are documented but sorry, link eludes my memory at the moment. Servicing, perf and disk footprint are some of the differences.
SelfContained has its merits but it's not without its downsides. Neither option (MSIX vs SelfContained) fully supplants the other which is why we support both :-)
Once again - Self-contained distribution is not what I want. That's why I wrote that. I wrote what I meant and I meant what I wrote.
I want unpackaged apps to just load a bunch of DLLs, rather than activate a package. That way it works when they're CreateProcessWithLogon
d.
That's possible today, but with a lot of hackery. Self-contained distribution demonstartes that it's possible, but self-contained distribution requires the bunch of DLLs to be located in the same folder as the EXE. WHICH IS NOT WHAT I WANT. I DON'T WANT TO DISTRIBUTE 60MB OF DLLS WITH MY PROGRAMS.
However, if a few calls to LoadLibraryExW
are changed to use different paths and/or different LOAD_LIBRARY_XYZ
flags, an unpackaged app can run without activating any packages AND without having the DLLs in the same folder as the EXE, but rather load them from a location shared with other programs.
I want the Windows App SDK to support it directly, without having to patch all those calls.
Whether or not this shared location is system-wide or user-specific I leave as an open question.
Whether or not this shared location should be the Windows App Runtime package folder (but still loaded just using LoadLibrary
!) or a separate folder I leave an open question.
But regardless of how you answer these questions it is possible for an unpackaged app written using the Windows App SDK to successfully load the Windows App Runtime from a location different than the one in which the EXE is located and without doing any package activation, and then subsequently successfully run.
The problem is that is requires patching the Windows App SDK to not require the DLLs be located in the same folder as the EXE. Please remove this requirement. This solves the runas issue.
You touch on some complex issues. The TL;DR you possibly can achieve what you want in a couple of ways:
Do either of these meet your needs?
Self-contained distribution...WHICH IS NOT WHAT I WANT
Just stating the facts, not trying to sell you SelfContained ;-) You don't need self-contained if you don't want AND you don't need the Bootstrapper with its related plumbing...if you only run on Win11. Alternatively, you may be able to use the Bootstrapper but avoid the issue with 'Run as different user'.
Solution #1 is actually quite simple:
<WindowsAppSdkBootstrapInitialize>false</WindowsAppSdkBootstrapInitialize>
Hmmm. That may not be well documented. I'll follow up on that.
The limitation is Windows' Dynamic Dependencies APIs require Win11. That's a problem if your app runs on Win10.
The alternative (solution #2)...
<WindowsAppSdkBootstrapInitialize>false</WindowsAppSdkBootstrapInitialize>
MddBootstrapInitialize2(...)
The limitation is this requires Windows >= 20H1 (it requires Windows' behavior not available on RS5 or 19H1).
WinAppSDK's bootstrap solution needs a process running with the identity of a main package that manifests a static dependency on WinAppSDK's framework package. There's a couple of ways to accomplish that:
WinAppSDK 1.0 did #1 but Packaged COM isn't supported1 in elevated processes on older systems (<Win11) so WinAppSDK 1.1 added #2. The decision which to use is made in IsLifetimeManagerViaEnumeration()
in dev/WindowsAppRuntime_BootstrapDLL/MddBootstrap.cpp line 541. The code refers to these as 'AppExtension' and 'Enumeration'. The decision logic:
MICROSOFT_WINDOWSAPPRUNTIME_DDLM_ALGORITHM
environment variable =0 ('Enumeration') or 1 ('AppExtension') Use what it saysThe limitations are (a) this requires Windows >= 20H1 and (b) doesn't support elevation if the servicing update isn't present.
1 Well, Packaged COM wasn't supported on <Win11. A Windows servicing update released recently (June?) addressed that. If a downlevel system has that update Packaged COM works for elevated processes.
I'll make a separate post regarding the other issues you raised and why this works the way it does.
The problem is that is requires patching the Windows App SDK
It's a little more complicated than that. The problem here is...
I don't see how that's relevant to unpackaged EXEs loading unpackaged DLLs.
It's not. That's why WindowsAppSDKSelfContained=true doesn't involve the Bootstrapper or Dynamic Dependencies, as then WinAppSDK isn't an MSIX package.
it is possible...without doing any package activation, and then subsequently successfully run.
Absolutely. Just use the Windows Dynamic Dependencies APIs instead of the Bootstrapper (which under the covers uses WinAppSDK's Dynamic Dependencies APIs).
We have 2 implementations of Dynamic Dependencies. Windows' implementation makes changes across Windows to support dynamic dependencies. WinAppSDK's implementation uses Windows in perfectly valid (if uncommon) ways to reliably produce the same net effect, without changes in Windows.
I want unpackaged apps to just load a bunch of DLLs, rather than activate a package
Loading the DLL is the goal. The activation plumbing is an implementation detail, but our options are limited (see previous post)
The (long-standing) limitation was unpackaged apps cannot use packaged goods. Not until Dynamic Dependencies came along i.e. Win11. Technically, yes, a process can LoadLibrary(absolutepath)
to a DLL in an MSIX package...but the Deployment stack doesn't know about that relationship. Deployment will think on one's using that package and is free to service it as necessary, up to and including removing the package from the system!
Servicing is the key. WinAppSDK's Dynamic Dependencies is all about giving unpackaged apps access to packaged goods in reliable ways Windows can understand. The Bootstrapper is just a specialized use of Dynamic Dependencies, to enable dynamic use of WinAppSDK (chicken/egg problem: DynDep API's in the framework package, how do you call it to use the framework package when you can't yet access the framework package... The Bootstrap API!).
Whether or not this shared location...
The problem is servicing. How do we get the necessary bits distributed, deployed (install, update etc) and reliably managed? That's not as easy as it sounds (well, assuming it even sounds easy :P). MSIX provides a strong solution here.
It's just <sic> a matter of balancing the requirements with the technical limitations.
But why isn't that enough? Why do you call IAAM->ActivateApplication instead of just LoadLibrary?
TL;DR because changing previously released versions of Windows (aka 'downlevel') isn't viable, so we trick existing Windows to work the way we need.
The Deployment stack in Windows knows not to service a package in use. HOW it knows is the key.
Deployment determines a package is in use if 1+ running process with package identity has the package in its package graph. For instance, if the calculator app in the Microsoft.WindowsCalculator package has a dependency on VCLibs then when you run the Calculator its package graph is [Calculator, VCLibs]. When running the app's process has a package identity in its process token (e.g. Microsoft.WindowsCalculator_10.2103.8.0_x64__8wekyb3d8bbwe
) so the Deployment stack knows to connect the dots to recognize that package and its dependencies are in use and not to do things that would break the running process (don't service the Calculator package, don't remove that VCLibs package, etc).
Deployment knows the process' package graph because it computed the package graph when the package was registered for the user. Calculator's appxmanifest.xml
declares
<PackageDependency Name="Microsoft.VCLibs.140.00" MinVersion="14.0.29231.0" Publisher="CN=Microsoft Corporation, O=Microsoft Corporation, L=Redmond, S=Washington, C=US"/>
When the Calculator package was registered for the user Deployment resolves this dependency to a specific package (version>=minversion, a compatible ProcessorArchitecture, etc) and saves the resolved package graph. During later Deployment operations Deployment checks resolved static package dependencies to determine if we're being asked to service a package that's in use and if so, don't proceed.2
2It's a little more involved (e.g. if the Force
option's specified Deployment may terminate processes) but not significant for this discussion.
Notice the word 'static'. The package graph is resolved at install-time based on static manifested dependencies. Deployment had no concept of processes using packages without a static dependency.
Until Win11.
Dynamic Dependencies introduced in Win11 didn't just add some API functions. We also changed Windows - changed the Deployment stack - to know about packages in use dynamically.
That's why AddPackageDependency() has no mention of a 'bootstrapping' or helper processes -- we just changed Windows to understand this behavior.
That's great -- if you write apps that only work on >=Win11.
Lots of developers need to run on older (<Win11) systems. Windows App SDK supports Windows down to RS5. The question is how to accomplish that.
One option is to service Windows. We could have backported Windows' Dynamic Dependencies work to older systems. But that's problematic for multiple reasons including
The alternative is to do things in ways downlevel Windows understands producing equivalent results. And thus, the bootstrapper and helper process. WinAppSDK refers to this as "polyfill" -- leverage Windows functionality where we can, and supplement with our own implementation where necessary. See Version support.
This leads to the obvious question, "Why not make WinAppSDK's Dynamic Dependencies use Windows' APIs on Win11 and only do the polyfill on Win10?" That's always been the long-term goal1 we just haven't done it (yet). But this won't help apps on Win10 so we've been focusing on some higher priority work first (e.g. elevation).
1 You may have noticed WinAppSDK's Dynamic Dependencies API is verrrry similar to Win11's, almost as if they were designed that way... ;-) That's not an accident.
I want unpackaged apps to just load a bunch of DLLs, rather than activate a package
Loading the DLL is the goal. The activation plumbing is an implementation detail, but our options are limited.
The (long-standing) limitation is unpackaged apps cannot use packaged goods. Not until Dynamic Dependencies came long i.e. Win11. Technically, yes, a process can LoadLibrary(absolutepath)
to a DLL in an MSIX package...but the Deployment stack doesn't know about that relationship. Deployment will think on one's using that package and is free to service it as necessary, up to and including removing the package.
Servicing is the key. WinAppSDK's Dynamic Dependencies is all about giving unpackaged apps access to packaged goods and doing so reliably in ways Windows can understand (or at least well enough not to break apps). The Bootstrapper is just a specialized (but necessary) use of Dynamic Dependencies to to enable dynamic use of WinAppSDK.
Whether or not this shared location...
The problem is servicing. How do we get the necessary bits distributed, deployed (install, update etc) and reliably managed? That's not as easy as it sounds (well, assuming it even sounds easy :P).
MSIX provides a strong solution here.
'Run as different user' brings some unusual details to the equation. I'm investigating other options but nothing yet. As you may have guessed, navigating Windows behavior to achieve the desired result is a bit tricky.
@DrusTheAxe, I read your post a few times and I've also read a bunch of other issues and discussion under this project that deal with related problems and I couldn't find an answer to my question. Maybe it's my fault for broadening the issue.
So I'll leave this issue for the specific problem of runas
and I'll ping once again the people you mentioned earlier (@jsidewhite @brialmsft @dhoehna) to see if they have something to share that would help here.
I may later open a separate issue for the broader matter, but before that I'd like to try one more time here, since this issue already exists and there's some history here.
I am asking that the Windows App SDK (i.e. the VSIX, NuGet package etc.), without modifying the Windows App Runtime in any way, allow completely unpackaged applications (i.e. not even using sparse packages, just "plain old Win32 applications") to use, without using any "package APIs" or relying on any other OS mechanism that doesn't support the runas
scenario, the Windows App Runtime that is already present on a system rather than only a copy of the Windows App Runtime that's located in the same folder as the EXE of those applications.
Are you saying this this is impossible or is there any other reason why this feature can't find itself in the next point release of the SDK?
without using any "package APIs" or relying on any other OS mechanism that doesn't support the
runas
scenario, the Windows App Runtime that is already present on a system rather than only a copy of the Windows App Runtime that's located in the same folder as the EXE of those applications.
So to summarize, you're asking for a solution that (1) enables unpackaged apps to use WinAppSDK (2) that's compatible with runas
and (3) doesn't involve any app-local copy (i.e. not WinAppSDK/SelfContained) and (4) doesn't involve MSIX if MSIX doesn't support runas
?
And constraint #4 is no MSIX -if- MSIX doesn't support runas
, but MSIX is OK if it does support runas
? That runas
without the app having its own private copy of WinAppSDK is the goal?
BTW are there Windows version constraints on your runas
scenario? If, say, it worked on Win11 but not Win10 would that meet your needs? If it worked down to 20H1? If it worked on Desktop but not Server? If... Please elaborate.
So to summarize, you're asking for a solution that (1) enables unpackaged apps to use WinAppSDK (2) that's compatible with
runas
and (3) doesn't involve any app-local copy (i.e. not WinAppSDK/SelfContained) and (4) doesn't involve MSIX if MSIX doesn't supportrunas
?And constraint #4 is no MSIX -if- MSIX doesn't support
runas
, but MSIX is OK if it does supportrunas
?
What do you mean by "involve MSIX"? If you're talking about the application - I think that's precluded by the requirement for the app being unpackaged. If you mean the runtime - for the purposes of this issue† I don't mind it being distributed as an MSIX. If you think that non-MSIX runtime is the preferred solution I have no objections, but that is not the requirement here. However it is implemented is fine by me and there are ways that work with the current runtime distributions, which is MSIX-based.
That
runas
without the app having its own private copy of WinAppSDK is the goal? Yes, exactly. Private copies have obvious disadvantages. The most important two for me are size and updates.BTW are there Windows version constraints on your
runas
scenario? If, say, it worked on Win11 but not Win10 would that meet your needs? If it worked down to 20H1? If it worked on Desktop but not Server? If... Please elaborate.
That's a somewhat complicated question to answer but the short answer is: We need to support Server SKUs and we need to support builds starting with 17763. Luckily that's the version you were supposed to support in the first place and the website and documentation still say that. My investigation show that it can work on Windows 10 1809 - I'm sure that it works on 17763.1577 and as far as I recall it works on 17763.107 - and I see no reason why the same shouldn't work on Server SKUs too.
The slightly longer answer is that our customers have 17 times more Server 2019 instances than Server 2022 instances. Obviously it would be better to also support Server 2016, but that ship has sailed. Assuming we can find a way to live with that (e.g. maintain two versions until pre-2019 goes away, use WASDK only for certain elements that would be offered only starting with 2019 etc.) 2019 is the bare minimum. If 2019 isn't supported that means nothing is.
† I think there are advantages to also distributing the runtime in an unpackaged form, but spreading too much here was probably wrong of me. In time I hope to address that in a separate issue or discussion, unless you convince yourself that a non-MSIX runtime is desirable before I get the chance.
So I'll leave this issue for the specific problem of
runas
> MSIX - Does Not Support "INSTALL FOR ALL USERS" and by extension "RUN AS ANOTHER USER".
>> Since WindowsAppSDK uses MSIX based runtime, you simply can't leverage WindowsAppSDK features for "ALL USERS on the SAME MACHINE" in your Apps until MSIX limitations are fixed in the OS. Simple as that.
>>> Any attempt to support "ALL USERS" will essentially result in ditching MSIX in its current form.
>>>> See below {
No, "Machine Wide Provisioning" does not count as it still registers/installs the app per User basis which is essentially "single user installation". }
What do you mean by "involve MSIX"? ...I don't mind it being distributed as an MSIX. If you think that non-MSIX runtime is the preferred solution I have no objections, but that is not the requirement here. However it is implemented is fine by me and there are ways that work with the current runtime distributions, which is MSIX-based.
Yes, that's what I meant. Thank you for the detailed response.
That
runas
without the app having its own private copy of WinAppSDK is the goal? Yes, exactly. Private copies have obvious disadvantages. The most important two for me are size and updates.
Very true. The MSIX (framework-dependent) solution is recommended in this case.
There are OS limitations in runas+MSIX support causing the problem here. That's the heart of the issue and unfortunately I haven't been able to find a creative workaround.
For the OS limitation, please use FeedbackHub to submit your feedback about the issue to get it on the appropriate OS teams' radar.
+@mikebattista @MikeHillberg @kanismohammed as FYI
My investigation show that it can work on Windows 10 1809 - I'm sure that it works on 17763.1577 and as far as I recall it works on 17763.107 - and I see no reason why the same shouldn't work on Server SKUs too.
Server is similar to Desktop but can have variances due to release schedules, the underlying codebase/builds used for a release, servicing updates, system definition and configuration, conditional code in Windows binaries themselves (e.g. if (IsServer()) {...}
or IsDesktop()
etc) and other factors. So while you might assume they're equivalent and often be right, that's not always the case. Very much a devil's in the details question.
I think there are advantages to also distributing the runtime in an unpackaged form, but spreading too much here was probably wrong of me. In time I hope to address that in a separate issue or discussion, unless you convince yourself that a non-MSIX runtime is desirable before I get the chance.
Doubtful ;-) Aside from the obvious but surmountable issues (technical and non-technical) the hard nut is servicing. How do you update such a thing -- Distribute how? Apply the update when it's in use? Minimize disk space impact? Among other factors. "Install" something is easy (relatively speaking), it's "Update" that's hard. MSIX solves these issues.
Inventing a new solution is a non-trivial undertaking, and an ongoing tax of splintered redundant efforts (there's only so many devs and hours to go around). If MSIX has issues it's likely more sensible (and justifiable :-)) to address them than invent a new redundant divergent solution.
There are OS limitations in runas+MSIX support causing the problem here. That's the heart of the issue and unfortunately I haven't been able to find a creative workaround.
For the OS limitation, please use FeedbackHub to submit your feedback about the issue to get it on the appropriate OS teams' radar.
An omission on my part, since it was obvious to me and I forgot to specify it explicitly: I am not interested in anything that depends, requires or otherwise entails operating system updates. Whatever operating system we support, Windows 1w version 2xyz for any w, x, y, z, we need to support it starting from the "RTM" (i.e. for every build the first UBR that was publicly released rather than only as Insider). "We support Windows 10 20H2, but only if you have the October 11, 2022 update" is just as useless as "we support only Windows 11 build 25xyz" to me.
My machine is always updates but my customers' machine aren't necessarily, and it is on their machines I need to run. Their reasons may be poor but that's irrelevant. Judging their decisions on this issue is not what they're paying me for.
Again I remind that since it was called "Project Reunion" till this very day the Windows App SDK promised and promises "downlevel support" and "not relying on operating system servicing/updates" (paraphrases). If any bug is resolved with an OS update we're back to UWP. Again, one of the main supposed benefits of WASDK over UWP was supposedly its independence of OS updates.
Server is similar to Desktop but can have variances due to release schedules, the underlying codebase/builds used for a release, servicing updates, system definition and configuration, conditional code in Windows binaries themselves (e.g.
if (IsServer()) {...}
orIsDesktop()
etc) and other factors. So while you might assume they're equivalent and often be right, that's not always the case. Very much a devil's in the details question.
In principle, generally, that's true. In practice, regarding the specific workaround I tried, I don't believe there's a dependency on workstation-specific behavior, but that's why I was careful and said that I've tested down to 1809 but haven't tested on Server yet. If any when I will get to operationalizing it from the "less than PoC" state it is now I will of course test it on Servers too.
I can confirm this issue still happens under:
The error shown on Event Viewer:
Describe the bug
App crashes during load when run as a different user than the one to whom the Terminal Services session belongs.
Steps to reproduce the bug
<WindowsPackageType>None</WindowsPackageType>
to the.vcxproj
.<AppxPackage>true</AppxPackage>
to<AppxPackage>false</AppxPackage>
Expected behavior
Program running.
Screenshots
NuGet package version
1.1.0
Packaging type
Unpackaged
Windows version
Windows 11 version 21H2 (22000)
IDE
Visual Studio 2022