May 12th, 2026
likeintriguing5 reactions

Is This a Packaged Process?

Principal Software Engineer

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:

  1. The process HAS package identity
  2. The process LACKS package identity
  3. 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_BUFFER indicates the process has package identity (the call failed only because a buffer was not provided)
  • APPMODEL_ERROR_NO_PACKAGE indicates 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.

Author

Howard Kapustein
Principal Software Engineer

MSIX Development Engineer/Architect

6 comments

Sort by :
  • Me Gusta 2 weeks ago

    Is there a specific reason why GetCurrentPackageFullName is the recommended function? Most of the GetCurrentPackage functions would seemingly do the job just as well.

    • Howard KapusteinMicrosoft employee Author

      Functionally, yes, but GetCurrentPackageFullName’s implementation is fractionally leaner than others for this end.

      Not a major difference but…waste not want not 🙂

  • Dongle ͏

    The C# sample code here isn't compatible with Native AOT. Here's a version that does (note, it requires AllowUnsafeBlocks=true):
    <code>

    Read more
    • Howard KapusteinMicrosoft employee Author · Edited

      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,...

      Read more
  • Paulo Pinto · Edited

    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>

    Read more
    • Howard KapusteinMicrosoft employee Author · Edited

      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...

      Read more