Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
b57ac41
GetPackageFile()
DrusTheAxe Jun 23, 2025
5996e25
Merge branch 'main' into user/drustheaxe/FindPackageFile
DrusTheAxe Jun 23, 2025
10643e9
Fix bugs. Add start of tests
DrusTheAxe Jun 29, 2025
3e74652
Don't checkin *.bak
DrusTheAxe Jun 29, 2025
d41dd14
Added Package activatable class to manifests
DrusTheAxe Jun 29, 2025
9912d17
Partial implementation
DrusTheAxe Aug 7, 2025
99bc5dd
Merge branch 'main' into user/drustheaxe/FindPackageFile
DrusTheAxe Sep 14, 2025
7f25624
GetFilePath initial implementation
DrusTheAxe Dec 14, 2025
9fcfd8a
Merge branch 'main' into user/drustheaxe/FindPackageFile
DrusTheAxe Dec 14, 2025
115ef4e
Improved tests. Added dllmain
DrusTheAxe Dec 28, 2025
f93b30d
Merge branch 'main' into user/drustheaxe/FindPackageFile
DrusTheAxe Dec 28, 2025
c70c361
More tests. Added dev\common to various test projects' Include path t…
DrusTheAxe Jan 25, 2026
4ffc762
Test fixes
DrusTheAxe Jan 25, 2026
4416815
Fixed missing build dependencies
DrusTheAxe Jan 25, 2026
67cf0f1
More tests. Checkpointing code in progress
DrusTheAxe Jan 26, 2026
2d14aff
Added TAEF VERIFY_* support for std::filesystem::path. More fixes and…
DrusTheAxe Jan 26, 2026
85716e8
Expanded tests. Bug fixes
DrusTheAxe Feb 2, 2026
16b7acf
Added missing test type
DrusTheAxe Feb 2, 2026
e3004bb
Checkpoint
DrusTheAxe Feb 9, 2026
9db4965
Fixes
DrusTheAxe Feb 9, 2026
9f4fbc9
Removed SearchStatic/DynamicDependencies options as we can't determin…
DrusTheAxe Feb 9, 2026
7f6c9af
Tweaks
DrusTheAxe Feb 9, 2026
1dfa5c1
Merge branch 'main' into user/drustheaxe/FindPackageFile
DrusTheAxe Feb 9, 2026
f491637
Fixed bad merge
DrusTheAxe Feb 9, 2026
110cda8
Fixed warnings
DrusTheAxe Feb 9, 2026
996118e
Fixed warning
DrusTheAxe Feb 9, 2026
653d645
Fix to dynamically use GetPackagePathByFullName2 if available
DrusTheAxe Feb 16, 2026
341aa90
Merge branch 'main' into user/drustheaxe/FindPackageFile
DrusTheAxe Feb 17, 2026
f586727
Merge branch 'main' into user/drustheaxe/FindPackageFile
DrusTheAxe Mar 15, 2026
919d295
Merge branch 'main' into user/drustheaxe/FindPackageFile
DrusTheAxe Mar 18, 2026
d90ca7d
Can't use RuntimeBehavior/TrustLevel on old pre-20H1 systems. Switche…
DrusTheAxe Mar 18, 2026
6d2de6a
Fixed Decimal test
DrusTheAxe Mar 21, 2026
572cfbc
Merge branch 'user/drustheaxe/FindPackageFile' of https://github.com/…
DrusTheAxe Mar 21, 2026
31b010e
Fix other decimal test
DrusTheAxe Mar 21, 2026
4a08b7c
Fixed GetPackagePathByFullName2IfSupported to work on RS5
DrusTheAxe Mar 21, 2026
f1cfa89
Merge branch 'main' into user/drustheaxe/FindPackageFile
DrusTheAxe Mar 21, 2026
c302562
Fix tests
DrusTheAxe Mar 22, 2026
9d2b657
Test fixes
DrusTheAxe Mar 22, 2026
3459eac
Fixed typo
DrusTheAxe Mar 22, 2026
be71808
Merge branch 'user/drustheaxe/FindPackageFile' of https://github.com/…
DrusTheAxe Mar 22, 2026
475784c
Fix downlevel support
DrusTheAxe Mar 24, 2026
94ae526
Merge branch 'main' into user/drustheaxe/FindPackageFile
DrusTheAxe Mar 24, 2026
eefa063
Fixed package not found should == file not found
DrusTheAxe Mar 24, 2026
a0363d5
Fixed typo
DrusTheAxe Mar 24, 2026
50e998b
Fixed tests. Removed dead code
DrusTheAxe Mar 25, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@
*.userosscache
*.sln.docstates

# Backup files
*.[Bb][Aa][Kk]

# full nuget directory
[Bb]uild/FullNuget

Expand Down
163 changes: 163 additions & 0 deletions WindowsAppRuntime.sln

Large diffs are not rendered by default.

3 changes: 3 additions & 0 deletions build/NuSpecs/AppxManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,9 @@
<ActivatableClass ActivatableClassId="Microsoft.Windows.ApplicationModel.DynamicDependency.PackageDependencyContext" ThreadingModel="both" />
<ActivatableClass ActivatableClassId="Microsoft.Windows.ApplicationModel.DynamicDependency.PackageDependencyRank" ThreadingModel="both" />

<!-- Package -->
<ActivatableClass ActivatableClassId="Microsoft.Windows.ApplicationModel.Package" ThreadingModel="both" />
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Package

Should PackageGraph also be added here?


<!-- WinAppSDK Deployment -->
<ActivatableClass ActivatableClassId="Microsoft.Windows.ApplicationModel.WindowsAppRuntime.DeploymentManager" ThreadingModel="both" />
<ActivatableClass ActivatableClassId="Microsoft.Windows.ApplicationModel.WindowsAppRuntime.DeploymentResult" ThreadingModel="both" />
Expand Down
210 changes: 210 additions & 0 deletions dev/Common/AppModel.Package.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,12 @@

#include <appmodel.h>

#include <filesystem>
#include <string>

#include <AppModel.Identity.h>
#include <ExportLoader.h>
#include <IsWindowsVersion.h>

namespace AppModel::Package
{
Expand Down Expand Up @@ -79,6 +84,211 @@ inline std::tuple<std::wstring, PACKAGE_VERSION, std::uint32_t, std::wstring, st
{
return ParsePackageFullName(packageFullName.c_str());
}

namespace details
{
// Helper: build the return type from PCWSTR
template <typename TString>
inline TString MakeFromPCWSTR(PCWSTR s)
{
if constexpr (std::is_same_v<TString, std::wstring>)
{
return s ? std::wstring{s} : std::wstring{};
}
else
{
// For WIL unique string wrappers, use WIL's maker.
// WIL string maker functions accept PCWSTR and return a unique_*_string wrapper. [1](https://github-wiki-see.page/m/microsoft/wil/wiki/String-helpers)
return wil::make_unique_string<TString>(s);
}
}

// GetPackagePathByFullName2 requires >=19H1
typedef LONG (WINAPI* GetPackagePathByFullName2Function)(
PCWSTR packageFullName,
PackagePathType packagePathType,
UINT32* pathLength,
PWSTR path);

inline wil::unique_hmodule g_dll_apiset_appmodel_runtime_1_3;
inline GetPackagePathByFullName2Function g_getPackagePathByFullName2{};
inline std::once_flag g_onceFlag{};

inline void initialize()
{
wil::unique_hmodule dll;
if (::ExportLoader::Load(L"api-ms-win-appmodel-runtime-l1-1-3.dll", wil::out_param(dll)))
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Load

This function returns HRESULT but it's checked as if a bool

{
return;
}
if (dll)
{
GetPackagePathByFullName2Function getPackagePathByFullName2{};
if (FAILED(::ExportLoader::GetFunctionIfExists<GetPackagePathByFullName2Function>(dll.get(), "GetPackagePathByFullName2", &getPackagePathByFullName2)))
{
return;
}
if (getPackagePathByFullName2)
{
g_dll_apiset_appmodel_runtime_1_3 = std::move(dll);
g_getPackagePathByFullName2 = std::move(getPackagePathByFullName2);
}
}
}

/// Get the path for a package, if GetPackagePathByFullName2() is available.
/// Return an empty path if the PackagePathType isn't supported on current platform (*pathLength=0, *path="").
/// @see https://learn.microsoft.com/en-us/windows/win32/api/appmodel/nf-appmodel-getpackagepathbyfullname2
inline HRESULT GetPackagePathByFullName2IfSupported(
_In_ PCWSTR packageFullName,
PackagePathType packagePathType,
std::uint32_t* pathLength,
_Out_writes_opt_(*pathLength) PWSTR path)
{
// Availability is a matter of timeline:
// * PackagePathType_Install is available since Win8
// * PackagePathType_Mutable is available since 19H1
// * PackagePathType_Effective is available since 19H1
// * PackagePathType_MachineExternalLocation is available since 20H1
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

PackagePathType_MachineExternalLocation

The value is called "PackagePathType_MachineExternal" (without "Location"). Similarly for PackagePathType_UserExternal and PackagePathType_EffectiveExternal below.

// * PackagePathType_UserExternalLocation is available since 20H1
// * PackagePathType_EffectiveExternalLocation is available since 20H1
// GetPackagePathByFullName() is available since Win8
// GetPackagePathByFullName2() is available since 19H1 (though not all PackagePathType values were supported that early)
//
// Treat asks for locations not supported by the current system the same as not-found

if (::WindowsVersion::IsWindows10_20H1OrGreater() ||
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

IsWindows10_20H1OrGreater

Instead of checking by version number, wouldn't it be better to check by feature availability?

if (SUCCEEDED(initialize) && SUCCEEDED(g_getPackagePathByFullName2(...))
{
  // Got path through GetPackagePathByFullName2
}
else if (SUCCEEDED(GetPackagePathByFullName(...))
{
  // GetPackagePathByFullName2 is not available, but GetPackagePathByFullName works
}
else
{
  // Cannot get path
}

(::WindowsVersion::IsWindows10_19H1OrGreater() &&
((packagePathType == PackagePathType_Install) || (packagePathType == PackagePathType_Mutable) || (packagePathType == PackagePathType_Effective))))
{
std::call_once(g_onceFlag, initialize);
RETURN_HR_IF_NULL(E_NOTIMPL, g_getPackagePathByFullName2);

RETURN_IF_FAILED(g_getPackagePathByFullName2(packageFullName, packagePathType, pathLength, path));
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

g_getPackagePathByFullName2

This returns win32 error (LONG), not HRESULT

}
else if ((packagePathType == PackagePathType_Install) || (packagePathType == PackagePathType_Effective))
{
// Only Install location is supported by the current system
// Effective is thus equivalent to Install
// Either way, rock it old school...
RETURN_IF_FAILED(::GetPackagePathByFullName(packageFullName, pathLength, path));
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

GetPackagePathByFullName

This returns win32 error (LONG), not HRESULT

}
else
{
// The requested location isn't possible on the current system
if (path && (*pathLength > 0))
{
*path = L'\0';
}
*pathLength = 0;
}
return S_OK;
}
}

/// Get the path for a package.
/// @see https://learn.microsoft.com/en-us/windows/win32/api/appmodel/nf-appmodel-getcurrentpackagepath2
template <typename Tstring>
inline Tstring GetPath(_In_ PCWSTR packageFullName, PackagePathType packagePathType)
{
// Paths can be long but typically short(ish). We can use a quick fixed buffer
// as an optimization and fallback to dynamic allocation if need be
WCHAR path[MAX_PATH]{};
uint32_t pathLength{ ARRAYSIZE(path) };
const auto hr{ details::GetPackagePathByFullName2IfSupported(packageFullName, packagePathType, &pathLength, path) };
if (SUCCEEDED(hr))
{
if (pathLength > 0)
{
return details::MakeFromPCWSTR<Tstring>(path);
}
else
{
return Tstring{};
}
}
else if ((hr == HRESULT_FROM_WIN32(ERROR_NOT_FOUND)) ||
(hr == HRESULT_FROM_WIN32(APPMODEL_ERROR_NO_MUTABLE_DIRECTORY)) ||
(hr == E_NOTIMPL))
{
return Tstring{};
}
else if (hr != HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER))
{
THROW_HR_MSG(hr, "PackageFullName=%ls PackagePathType=%d", packageFullName ? packageFullName : L"<null>", static_cast<int>(packagePathType));
}

// It's bigger than a breadbox. Allocate memory
std::unique_ptr<WCHAR[]> pathBuffer{ std::make_unique<WCHAR[]>(pathLength) };
THROW_IF_WIN32_ERROR_MSG(details::GetPackagePathByFullName2IfSupported(packageFullName, packagePathType, &pathLength, pathBuffer.get()),
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

GetPackagePathByFullName2IfSupported

This function returns HRESULT but is checked for win32 error

"PackageFullName=%ls PackagePathType=%d", packageFullName ? packageFullName : L"<null>", static_cast<int>(packagePathType));
return details::MakeFromPCWSTR<Tstring>(pathBuffer.get());
}

/// Get the install path for a package.
/// @return null or empty string if the package isn't visible.
/// @see https://learn.microsoft.com/en-us/windows/win32/api/appmodel/ne-appmodel-packagepathtype
template <typename Tstring>
inline Tstring GetInstallPath(_In_ PCWSTR packageFullName)
{
return GetPath<Tstring>(packageFullName, PackagePathType_Install);
}

/// Get the mutable path for a package.
/// @return null or empty string if the package isn't visible to the caller or has no mutable path.
/// @see https://learn.microsoft.com/en-us/windows/win32/api/appmodel/ne-appmodel-packagepathtype
template <typename Tstring>
inline Tstring GetMutablePath(_In_ PCWSTR packageFullName)
{
return GetPath<Tstring>(packageFullName, PackagePathType_Mutable);
}

/// Get the machine external path for a package.
/// @return null or empty string if the package isn't visible to the caller or has no machine external path.
/// @see https://learn.microsoft.com/en-us/windows/win32/api/appmodel/ne-appmodel-packagepathtype
template <typename Tstring>
inline Tstring GetMachineExternalPath(_In_ PCWSTR packageFullName)
{
return GetPath<Tstring>(packageFullName, PackagePathType_MachineExternal);
}

/// Get the user external path for a package.
/// @return null or empty string if the package isn't visible to the caller or has no user external path.
/// @see https://learn.microsoft.com/en-us/windows/win32/api/appmodel/ne-appmodel-packagepathtype
template <typename Tstring>
inline Tstring GetUserExternalPath(_In_ PCWSTR packageFullName)
{
return GetPath<Tstring>(packageFullName, PackagePathType_UserExternal);
}

/// Get the effective external path for a package.
/// @return null or empty string if the package isn't visible to the caller or has no effective external path.
/// @see https://learn.microsoft.com/en-us/windows/win32/api/appmodel/ne-appmodel-packagepathtype
template <typename Tstring>
inline Tstring GetEffectiveExternalPath(_In_ PCWSTR packageFullName)
{
return GetPath<Tstring>(packageFullName, PackagePathType_EffectiveExternal);
}

/// Get the effective path for a package.
/// @return null or empty string if the package isn't visible to the caller.
/// @see https://learn.microsoft.com/en-us/windows/win32/api/appmodel/ne-appmodel-packagepathtype
template <typename Tstring>
inline Tstring GetEffectivePath(_In_ PCWSTR packageFullName)
{
return GetPath<Tstring>(packageFullName, PackagePathType_Effective);
}

inline std::filesystem::path GetAbsoluteFilename(
PCWSTR packageFullName,
PCWSTR filename,
PackagePathType packageType)
{
const auto path{ ::AppModel::Package::GetPath<std::wstring>(packageFullName, packageType) };
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

GetPath

This might return empty string - should that be handled as a special case?
std::filesystem::absolute("" / filename) might return a bogus result.

std::filesystem::path pathName{ path };
pathName /= filename;
return std::filesystem::absolute(pathName);
}
}

#endif // __APPMODEL_PACKAGE_H
6 changes: 3 additions & 3 deletions dev/Common/AppModel.PackageGraph.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,18 +10,18 @@ namespace AppModel::PackageGraph
{
inline HRESULT GetCurrentPackageGraph(
const UINT32 flags,
uint32_t& packageInfoCount,
std::uint32_t& packageInfoCount,
const PACKAGE_INFO*& packageInfo,
wil::unique_cotaskmem_ptr<BYTE[]>& buffer)
{
packageInfoCount = 0;
packageInfo = nullptr;

uint32_t bufferLength{};
std::uint32_t bufferLength{};
LONG rc{ ::GetCurrentPackageInfo(flags, &bufferLength, nullptr, &packageInfoCount) };
if ((rc == APPMODEL_ERROR_NO_PACKAGE) || (packageInfoCount == 0))
{
// No packages. Were done
// No packages. We're done
return S_OK;
}
RETURN_HR_IF(HRESULT_FROM_WIN32(rc), rc != ERROR_INSUFFICIENT_BUFFER);
Expand Down
2 changes: 1 addition & 1 deletion dev/Common/Common.vcxitems.filters
Original file line number Diff line number Diff line change
Expand Up @@ -59,4 +59,4 @@
<Filter>Source Files</Filter>
</ClCompile>
</ItemGroup>
</Project>
</Project>
43 changes: 40 additions & 3 deletions dev/Common/ExportLoader.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,19 @@

namespace ExportLoader
{
inline HRESULT Load(PCWSTR moduleName, _Out_ HMODULE* module)
{
auto hmodule{ ::LoadLibraryExW(moduleName, nullptr, LOAD_LIBRARY_SEARCH_SYSTEM32) };
if (!hmodule)
{
const auto rc{ GetLastError() };
RETURN_HR_IF_MSG(HRESULT_FROM_WIN32(rc), rc != ERROR_MOD_NOT_FOUND, "%ls", moduleName);
RETURN_HR(E_NOTIMPL);
}
*module = hmodule;
return S_OK;
}

inline HMODULE Load(PCWSTR moduleName)
{
auto hmodule{ ::LoadLibraryExW(moduleName, nullptr, LOAD_LIBRARY_SEARCH_SYSTEM32) };
Expand All @@ -19,17 +32,41 @@ inline HMODULE Load(PCWSTR moduleName)
}

template <typename T>
inline T GetFunction(HMODULE module, PCSTR functionName)
inline HRESULT GetFunctionIfExists(HMODULE module, PCSTR functionName, _Out_ T* function)
{
auto fn{ reinterpret_cast<T>(::GetProcAddress(module, functionName)) };
if (!fn)
{
const auto rc{ GetLastError() };
RETURN_HR_IF_MSG(HRESULT_FROM_WIN32(rc), rc != ERROR_PROC_NOT_FOUND, "%hs", functionName);
}
*function = fn;
return S_OK;
}

template <typename T>
inline T GetFunctionIfExists(HMODULE module, PCSTR functionName)
{
auto function{ reinterpret_cast<T>(::GetProcAddress(module, functionName)) };
if (!function)
{
const auto rc{ GetLastError() };
THROW_HR_IF_MSG(HRESULT_FROM_WIN32(rc), rc != ERROR_PROC_NOT_FOUND, "%hs", functionName);
THROW_HR(E_NOTIMPL);
if (rc == ERROR_PROC_NOT_FOUND)
{
return nullptr;
}
THROW_WIN32_MSG(rc, "%hs", functionName);
}
return function;
}

template <typename T>
inline T GetFunction(HMODULE module, PCSTR functionName)
{
auto function{ GetFunctionIfExists<T>(module, functionName) };
THROW_HR_IF_NULL_MSG(E_NOTIMPL, function, "%hs", functionName);
return function;
}
}

#endif // defined(__EXPORTLOADER_H)
4 changes: 3 additions & 1 deletion dev/Common/IsWindowsVersion.h
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
// Copyright (c) Microsoft Corporation and Contributors.
// Copyright (c) Microsoft Corporation and Contributors.
// Licensed under the MIT License.

#ifndef __ISWINDOWSVERSION_H
#define __ISWINDOWSVERSION_H

#include <VersionHelpers.h>

#include <wil/resource.h>

namespace WindowsVersion
{
inline bool IsExportPresent(
Expand Down
32 changes: 32 additions & 0 deletions dev/Common/TerminalVelocityFeatures-PackageRuntime.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
// Copyright (c) Microsoft Corporation and Contributors.
// Licensed under the MIT License.

// THIS FILE IS AUTOMATICALLY GENERATED; DO NOT EDIT IT

// INPUT FILE: dev\Common\TerminalVelocityFeatures-PackageRuntime.xml
// OPTIONS: -Channel Experimental -Language C++ -Namespace Microsoft.Windows.ApplicationModel -Path dev\Common\TerminalVelocityFeatures-PackageRuntime.xml -Output dev\Common\TerminalVelocityFeatures-PackageRuntime.h

#if defined(__midlrt)
namespace features
{
feature_name Feature_PackageRuntime = { DisabledByDefault, FALSE };
}
#endif // defined(__midlrt)

// Feature constants
#define WINDOWSAPPRUNTIME_MICROSOFT_WINDOWS_APPLICATIONMODEL_FEATURE_PACKAGERUNTIME_ENABLED 1

#if defined(__cplusplus)

namespace Microsoft::Windows::ApplicationModel
{

__pragma(detect_mismatch("ODR_violation_WINDOWSAPPRUNTIME_MICROSOFT_WINDOWS_APPLICATIONMODEL_FEATURE_PACKAGERUNTIME_ENABLED_mismatch", "AlwaysEnabled"))
struct Feature_PackageRuntime
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Feature_PackageRuntime

The feature is defined but isn't used in code anywhere?

{
static constexpr bool IsEnabled() { return WINDOWSAPPRUNTIME_MICROSOFT_WINDOWS_APPLICATIONMODEL_FEATURE_PACKAGERUNTIME_ENABLED == 1; }
};

} // namespace Microsoft.Windows.ApplicationModel

#endif // defined(__cplusplus)
Loading
Loading