Sometimes you need to know whether the current process has package identity. Certain Windows features require it, and behaviors can differ—both technically and culturally.
For example, classic Windows applications often store settings in the registry, whereas packaged applications typically use ApplicationData.LocalSettings.
So how can your code determine whether it is running with package identity — that is, whether it is a packaged process?
Terminology
Let’s begin with clear and unambiguous definitions:
- Packaged process — a process that HAS package identity
- Unpackaged process — a process that LACKS package identity
We deliberately say process rather than application. The distinction matters: applications have processes, but not all processes are applications. For example, WinRT out-of-process (OOP) servers are processes, yet they are not applications in the traditional sense.
Possible Outcomes
When checking for package identity, there are three possible results:
- The process HAS package identity
- The process LACKS package identity
- An error occurred
The Canonical Check (C++)
The recommended approach uses GetCurrentPackageFullName:
...
#include <appmodel.h>
inline bool is_packaged_process()
{
UINT32 n{};
const auto rc{ ::GetCurrentPackageFullName(&n, nullptr) };
THROW_HR_IF_MSG(HRESULT_FROM_WIN32(rc), (rc != APPMODEL_ERROR_NO_PACKAGE) && (rc != ERROR_INSUFFICIENT_BUFFER), "GetCurrentPackageFullName rc=%d", rc);
return rc == ERROR_INSUFFICIENT_BUFFER;
}
Why this works
ERROR_INSUFFICIENT_BUFFERindicates the process has package identity (the call failed only because a buffer was not provided)APPMODEL_ERROR_NO_PACKAGEindicates the process lacks package identity- Any other value signals a genuine error condition
No-Throw Variant (C++)
If you prefer error codes over exceptions:
...
#include <appmodel.h>
inline HRESULT is_packaged_process_nothrow(bool& isPackagedProcess) noexcept
{
isPackagedProcess = false;
UINT32 n{};
const auto rc{ ::GetCurrentPackageFullName(&n, nullptr) };
RETURN_HR_IF_MSG(HRESULT_FROM_WIN32(rc),
(rc != APPMODEL_ERROR_NO_PACKAGE) && (rc != ERROR_INSUFFICIENT_BUFFER),
"GetCurrentPackageFullName rc=%d",
rc);
isPackagedProcess = (rc == ERROR_INSUFFICIENT_BUFFER);
return S_OK;
}
Bonus: C# Equivalent
For those working in C#, here’s the equivalent implementation:
using System;
using System.ComponentModel;
using System.Runtime.InteropServices;
using System.Text;
internal static class PackageIdentity
{
private const uint ERROR_INSUFFICIENT_BUFFER = 122; // 0x007A
private const uint APPMODEL_ERROR_NO_PACKAGE = 15700; // 0x3D54
[DllImport("kernel32.dll", CharSet = CharSet.Unicode, ExactSpelling = true)]
private static extern uint GetCurrentPackageFullName(
ref int packageFullNameLength,
StringBuilder packageFullName);
public static bool IsPackagedProcess()
{
int n = 0;
uint rc = GetCurrentPackageFullName(ref n, null);
if (rc == ERROR_INSUFFICIENT_BUFFER)
{
return true;
}
else if (rc == APPMODEL_ERROR_NO_PACKAGE)
{
return false;
}
else
{
throw new Win32Exception((int)rc);
}
}
public static uint IsPackagedProcess_nothrow(out bool isPackaged)
{
int n = 0;
uint rc = GetCurrentPackageFullName(ref n, null);
isPackaged = (rc == ERROR_INSUFFICIENT_BUFFER);
return rc;
}
}
UPDATE 2026/05/13: Tweaked the C# variant, removing the incorrect SetLastError=true attribute and adding ExactSpelling=true for the right-er notation.
Is there a specific reason why GetCurrentPackageFullName is the recommended function? Most of the GetCurrentPackage functions would seemingly do the job just as well.
Functionally, yes, but GetCurrentPackageFullName’s implementation is fractionally leaner than others for this end.
Not a major difference but…waste not want not 🙂
The C# sample code here isn't compatible with Native AOT. Here's a version that does (note, it requires AllowUnsafeBlocks=true):
<code>
Fair point on AOT. Thanks for the tip. I don't use AOT often (most C# code I work on isn't supported by AOT, 'cause, reasons).
Good catch on `ExactSpelling = true`. I also shouldn't have specified `SetLastError`. I've tweaked the post's code with these corrections.
P.S. Your DllImport parameter `int* packageFullNameLength` should be `uint`. Not a problem here, until/unless the compiler flags the mismatched type.
You could also go AOT-friendly with `LibraryImport` and related tweaks, though that relies on source generators (ie SDK pipeline, analyzer infra and generator execution) so it requires a .csproj, not merely csc.exe. As my father used to say,...
First of all, kudos for including a C# version, still not a given in modern times when Microsoft talks about Windows APIs.
Second, I think the No-Throw Variant would be better off using modern C++ features like std::expected, instead of an out boolean parameter.
<code>
First of all, thank you 🙂
Second, I intentionally avoid (most) C++-isms in *_nothrow variants for clarity. Keeping it C(ish) makes it easier to adapt to other uses and languages (Rust, Python, yadda yadda). But I merely presented the canonical logic, not the only implementation. By all means fork the code for your own needs as you desire.
P.S. You forgot to remove the `isPackagedProcess = false;` when you moved to `std::expected`. More importantly, `RETURN_HR_IF_MSG()` is a macro ultimately ending in `return param1`. You'd need more changes to work with std::unexpected. If that tickles your fancy here's one possible fix:
<code>
You lose WIL's...