From ca1fa6dd09eede5bc8331d146253b91b9e34c3d9 Mon Sep 17 00:00:00 2001 From: Howard Kapustein Date: Fri, 29 Nov 2024 00:45:51 -0800 Subject: [PATCH 01/24] ApplicationData.GetForUnpackaged() initial skeleton --- dev/ApplicationData/ApplicationData.vcxitems | 2 + .../ApplicationData.vcxitems.filters | 6 + dev/ApplicationData/M.W.S.ApplicationData.cpp | 120 +++++++++++++--- dev/ApplicationData/M.W.S.ApplicationData.h | 3 + .../UnpackagedApplicationData.cpp | 130 ++++++++++++++++++ .../UnpackagedApplicationData.h | 44 ++++++ 6 files changed, 287 insertions(+), 18 deletions(-) create mode 100644 dev/ApplicationData/UnpackagedApplicationData.cpp create mode 100644 dev/ApplicationData/UnpackagedApplicationData.h diff --git a/dev/ApplicationData/ApplicationData.vcxitems b/dev/ApplicationData/ApplicationData.vcxitems index 32e3851ac8..84c3681acc 100644 --- a/dev/ApplicationData/ApplicationData.vcxitems +++ b/dev/ApplicationData/ApplicationData.vcxitems @@ -16,12 +16,14 @@ + + diff --git a/dev/ApplicationData/ApplicationData.vcxitems.filters b/dev/ApplicationData/ApplicationData.vcxitems.filters index 215fe7b0dc..a220c13730 100644 --- a/dev/ApplicationData/ApplicationData.vcxitems.filters +++ b/dev/ApplicationData/ApplicationData.vcxitems.filters @@ -17,6 +17,9 @@ Source Files + + Source Files + @@ -31,6 +34,9 @@ Header Files + + Header Files + diff --git a/dev/ApplicationData/M.W.S.ApplicationData.cpp b/dev/ApplicationData/M.W.S.ApplicationData.cpp index 89ecdecb0d..26a31d2400 100644 --- a/dev/ApplicationData/M.W.S.ApplicationData.cpp +++ b/dev/ApplicationData/M.W.S.ApplicationData.cpp @@ -99,11 +99,21 @@ namespace winrt::Microsoft::Windows::Storage::implementation } bool ApplicationData::IsMachinePathSupported() { + if (m_unpackagedApplicationData) + { + return m_unpackagedApplicationData->IsMachinePathSupported(); + } + const auto path{ _MachinePath(m_packageFamilyName) }; return !path.empty(); } hstring ApplicationData::LocalCachePath() { + if (m_unpackagedApplicationData) + { + return m_unpackagedApplicationData->LocalCachePath(); + } + if (!m_applicationData) { return winrt::hstring{}; @@ -112,6 +122,11 @@ namespace winrt::Microsoft::Windows::Storage::implementation } hstring ApplicationData::LocalPath() { + if (m_unpackagedApplicationData) + { + return m_unpackagedApplicationData->LocalPath(); + } + if (!m_applicationData) { return winrt::hstring{}; @@ -120,11 +135,21 @@ namespace winrt::Microsoft::Windows::Storage::implementation } hstring ApplicationData::MachinePath() { + if (m_unpackagedApplicationData) + { + return m_unpackagedApplicationData->MachinePath(); + } + const auto path{ _MachinePath(m_packageFamilyName) }; return winrt::hstring{ path.c_str() }; } hstring ApplicationData::SharedLocalPath() { + if (m_unpackagedApplicationData) + { + return m_unpackagedApplicationData->SharedLocalPath(); + } + if (!m_applicationData) { return winrt::hstring{}; @@ -143,6 +168,11 @@ namespace winrt::Microsoft::Windows::Storage::implementation } hstring ApplicationData::TemporaryPath() { + if (m_unpackagedApplicationData) + { + return m_unpackagedApplicationData->TemporaryPath(); + } + if (!m_applicationData) { return winrt::hstring{}; @@ -151,6 +181,11 @@ namespace winrt::Microsoft::Windows::Storage::implementation } winrt::Windows::Storage::StorageFolder ApplicationData::LocalCacheFolder() { + if (m_unpackagedApplicationData) + { + return m_unpackagedApplicationData->LocalCacheFolder(); + } + if (!m_applicationData) { return nullptr; @@ -159,6 +194,11 @@ namespace winrt::Microsoft::Windows::Storage::implementation } winrt::Windows::Storage::StorageFolder ApplicationData::LocalFolder() { + if (m_unpackagedApplicationData) + { + return m_unpackagedApplicationData->LocalFolder(); + } + if (!m_applicationData) { return nullptr; @@ -167,6 +207,11 @@ namespace winrt::Microsoft::Windows::Storage::implementation } winrt::Windows::Storage::StorageFolder ApplicationData::MachineFolder() { + if (m_unpackagedApplicationData) + { + return m_unpackagedApplicationData->MachineFolder(); + } + const auto path{ MachinePath() }; if (path.empty()) { @@ -176,6 +221,11 @@ namespace winrt::Microsoft::Windows::Storage::implementation } winrt::Windows::Storage::StorageFolder ApplicationData::SharedLocalFolder() { + if (m_unpackagedApplicationData) + { + return m_unpackagedApplicationData->SharedLocalFolder(); + } + if (!m_applicationData) { return nullptr; @@ -184,6 +234,11 @@ namespace winrt::Microsoft::Windows::Storage::implementation } winrt::Windows::Storage::StorageFolder ApplicationData::TemporaryFolder() { + if (m_unpackagedApplicationData) + { + return m_unpackagedApplicationData->TemporaryFolder(); + } + if (!m_applicationData) { return nullptr; @@ -192,6 +247,11 @@ namespace winrt::Microsoft::Windows::Storage::implementation } winrt::Microsoft::Windows::Storage::ApplicationDataContainer ApplicationData::LocalSettings() { + if (m_unpackagedApplicationData) + { + return m_unpackagedApplicationData->LocalSettings(); + } + if (!m_applicationData) { return nullptr; @@ -201,6 +261,11 @@ namespace winrt::Microsoft::Windows::Storage::implementation } winrt::Windows::Foundation::IAsyncAction ApplicationData::ClearAsync(winrt::Microsoft::Windows::Storage::ApplicationDataLocality locality) { + if (m_unpackagedApplicationData) + { + co_await m_unpackagedApplicationData->ClearAsync(locality); + } + if (!m_applicationData) { co_return; @@ -216,6 +281,11 @@ namespace winrt::Microsoft::Windows::Storage::implementation } winrt::Windows::Foundation::IAsyncAction ApplicationData::ClearPublisherCacheFolderAsync(hstring folderName) { + if (m_unpackagedApplicationData) + { + co_await m_unpackagedApplicationData->ClearPublisherCacheFolderAsync(folderName); + } + if (!m_applicationData) { co_return; @@ -224,6 +294,11 @@ namespace winrt::Microsoft::Windows::Storage::implementation } void ApplicationData::Close() { + if (m_unpackagedApplicationData) + { + return m_unpackagedApplicationData->Close(); + } + if (m_applicationData) { m_applicationData.Close(); @@ -231,6 +306,11 @@ namespace winrt::Microsoft::Windows::Storage::implementation } hstring ApplicationData::GetPublisherCachePath(hstring const& folderName) { + if (m_unpackagedApplicationData) + { + return m_unpackagedApplicationData->GetPublisherCachePath(folderName); + } + auto folder{ GetPublisherCacheFolder(folderName) }; winrt::hstring path; if (folder) @@ -239,25 +319,13 @@ namespace winrt::Microsoft::Windows::Storage::implementation } return path; } - winrt::Windows::Foundation::IAsyncAction ApplicationData::ClearMachineFolderAsync() - { - const auto path{ MachinePath() }; - - auto logTelemetry{ ApplicationDataTelemetry::ClearMachineFolderAsync::Start(path) }; - - auto strong{ get_strong() }; - - logTelemetry.IgnoreCurrentThread(); - co_await winrt::resume_background(); - auto logTelemetryContinuation{ logTelemetry.ContinueOnCurrentThread() }; - - const auto options{ wil::RemoveDirectoryOptions::KeepRootDirectory | wil::RemoveDirectoryOptions::RemoveReadOnly }; - wil::RemoveDirectoryRecursive(path.c_str(), options); - - logTelemetry.Stop(); - } winrt::Windows::Storage::StorageFolder ApplicationData::GetPublisherCacheFolder(hstring const& folderName) { + if (m_unpackagedApplicationData) + { + return m_unpackagedApplicationData->GetPublisherCacheFolder(folderName); + } + if (!m_applicationData) { return nullptr; @@ -279,6 +347,23 @@ namespace winrt::Microsoft::Windows::Storage::implementation throw; } } + winrt::Windows::Foundation::IAsyncAction ApplicationData::ClearMachineFolderAsync() + { + const auto path{ MachinePath() }; + + auto logTelemetry{ ApplicationDataTelemetry::ClearMachineFolderAsync::Start(path) }; + + auto strong{ get_strong() }; + + logTelemetry.IgnoreCurrentThread(); + co_await winrt::resume_background(); + auto logTelemetryContinuation{ logTelemetry.ContinueOnCurrentThread() }; + + const auto options{ wil::RemoveDirectoryOptions::KeepRootDirectory | wil::RemoveDirectoryOptions::RemoveReadOnly }; + wil::RemoveDirectoryRecursive(path.c_str(), options); + + logTelemetry.Stop(); + } std::filesystem::path ApplicationData::_MachinePath(hstring const& packageFamilyName) { // Path = HKLM\...apprepository...\Families\ApplicationData\...pkgfamilyname...\Machine @@ -297,7 +382,6 @@ namespace winrt::Microsoft::Windows::Storage::implementation } return std::filesystem::path{}; } - bool ApplicationData::_PathExists(std::filesystem::path const& path) { const std::filesystem::directory_entry directoryEntry{ path }; diff --git a/dev/ApplicationData/M.W.S.ApplicationData.h b/dev/ApplicationData/M.W.S.ApplicationData.h index 8256e50701..c985a883c1 100644 --- a/dev/ApplicationData/M.W.S.ApplicationData.h +++ b/dev/ApplicationData/M.W.S.ApplicationData.h @@ -5,6 +5,8 @@ #include "Microsoft.Windows.Storage.ApplicationData.g.h" +#include "UnpackagedApplicationData.h" + namespace winrt::Microsoft::Windows::Storage::implementation { struct ApplicationData : ApplicationDataT @@ -41,6 +43,7 @@ namespace winrt::Microsoft::Windows::Storage::implementation static hstring StorageFolderToPath(winrt::Windows::Storage::StorageFolder storageFolder); private: + std::unique_ptr<::Microsoft::Windows::Storage::UnpackagedApplicationData> m_unpackagedApplicationData; winrt::Windows::Storage::ApplicationData m_applicationData; winrt::hstring m_packageFamilyName; }; diff --git a/dev/ApplicationData/UnpackagedApplicationData.cpp b/dev/ApplicationData/UnpackagedApplicationData.cpp new file mode 100644 index 0000000000..9975bfe0df --- /dev/null +++ b/dev/ApplicationData/UnpackagedApplicationData.cpp @@ -0,0 +1,130 @@ +// Copyright (c) Microsoft Corporation and Contributors. +// Licensed under the MIT License. + +#include "pch.h" + +#include + +#include + +#include "UnpackagedApplicationData.h" + +#include "ApplicationDataTelemetry.h" + +namespace Microsoft::Windows::Storage +{ + UnpackagedApplicationData::UnpackagedApplicationData(winrt::hstring const& publisher, winrt::hstring const& product) : + m_publisher(publisher), + m_product(product), + m_localPath(), + m_machinePath() + { + } + bool UnpackagedApplicationData::IsMachinePathSupported() + { + const auto path{ _MachinePath(m_publisher, m_product) }; + return !path.empty(); + } + winrt::hstring UnpackagedApplicationData::LocalCachePath() + { + // ApplicationData.LocalCacheFolder has no unpackaged equivalent + throw winrt::hresult_not_implemented(); + } + winrt::hstring UnpackagedApplicationData::LocalPath() + { + if (m_localPath.empty()) + { + wil::unique_cotaskmem_string localAppData; + //TODO FOLDERID_LocalAppDataLow + THROW_IF_FAILED(SHGetKnownFolderPath(FOLDERID_LocalAppData, KF_FLAG_DEFAULT /*TODO KF_FLAG_CREATE | KF_FLAG_DONT_VERIFY*/, nullptr, wil::out_param(localAppData))); + std::filesystem::path path{ localAppData.get() }; + path /= m_publisher.c_str(); + path /= m_product.c_str(); + m_localPath = path.c_str(); + } + return m_localPath; + } + winrt::hstring UnpackagedApplicationData::MachinePath() + { + const auto path{ _MachinePath(m_publisher, m_product) }; + return winrt::hstring{ path.c_str() }; + } + winrt::hstring UnpackagedApplicationData::SharedLocalPath() + { + // ApplicationData.SharedLocalFolder has no unpackaged equivalent + throw winrt::hresult_not_implemented(); + } + winrt::hstring UnpackagedApplicationData::TemporaryPath() + { + throw winrt::hresult_not_implemented(); + } + winrt::Windows::Storage::StorageFolder UnpackagedApplicationData::LocalCacheFolder() + { + return winrt::Windows::Storage::StorageFolder::GetFolderFromPathAsync(LocalCachePath()).get(); + } + winrt::Windows::Storage::StorageFolder UnpackagedApplicationData::LocalFolder() + { + return winrt::Windows::Storage::StorageFolder::GetFolderFromPathAsync(LocalPath()).get(); + } + winrt::Windows::Storage::StorageFolder UnpackagedApplicationData::MachineFolder() + { + return winrt::Windows::Storage::StorageFolder::GetFolderFromPathAsync(MachinePath()).get(); + } + winrt::Windows::Storage::StorageFolder UnpackagedApplicationData::SharedLocalFolder() + { + return winrt::Windows::Storage::StorageFolder::GetFolderFromPathAsync(SharedLocalPath()).get(); + } + winrt::Windows::Storage::StorageFolder UnpackagedApplicationData::TemporaryFolder() + { + return winrt::Windows::Storage::StorageFolder::GetFolderFromPathAsync(TemporaryPath()).get(); + } + winrt::Microsoft::Windows::Storage::ApplicationDataContainer UnpackagedApplicationData::LocalSettings() + { + throw winrt::hresult_not_implemented(); + } + winrt::Windows::Foundation::IAsyncAction UnpackagedApplicationData::ClearAsync(winrt::Microsoft::Windows::Storage::ApplicationDataLocality locality) + { + throw winrt::hresult_not_implemented(); + } + winrt::Windows::Foundation::IAsyncAction UnpackagedApplicationData::ClearPublisherCacheFolderAsync(winrt::hstring folderName) + { + // ApplicationData.PublisherCacheFolder has no unpackaged equivalent + throw winrt::hresult_not_implemented(); + } + void UnpackagedApplicationData::Close() + { + throw winrt::hresult_not_implemented(); + } + winrt::hstring UnpackagedApplicationData::GetPublisherCachePath(winrt::hstring const& folderName) + { + // ApplicationData.PublisherCacheFolder has no unpackaged equivalent + throw winrt::hresult_not_implemented(); + } + winrt::Windows::Storage::StorageFolder UnpackagedApplicationData::GetPublisherCacheFolder(winrt::hstring const& folderName) + { + // ApplicationData.PublisherCacheFolder has no unpackaged equivalent + throw winrt::hresult_not_implemented(); + } + winrt::Windows::Foundation::IAsyncAction UnpackagedApplicationData::ClearMachineFolderAsync() + { + throw winrt::hresult_not_implemented(); + } + std::filesystem::path UnpackagedApplicationData::_MachinePath(winrt::hstring const& publisher, winrt::hstring const& product) + { + std::filesystem::path path; + if (path.empty()) + { + wil::unique_cotaskmem_string programData; + THROW_IF_FAILED(SHGetKnownFolderPath(FOLDERID_AppDataProgramData, KF_FLAG_DEFAULT /*TODO KF_FLAG_CREATE | KF_FLAG_DONT_VERIFY*/, nullptr, wil::out_param(programData))); + path = programData.get(); + path /= publisher.c_str(); + path /= product.c_str(); + } + return path; + } + + bool UnpackagedApplicationData::_PathExists(std::filesystem::path const& path) + { + throw winrt::hresult_not_implemented(); + } +} diff --git a/dev/ApplicationData/UnpackagedApplicationData.h b/dev/ApplicationData/UnpackagedApplicationData.h new file mode 100644 index 0000000000..b810303e06 --- /dev/null +++ b/dev/ApplicationData/UnpackagedApplicationData.h @@ -0,0 +1,44 @@ +// Copyright (c) Microsoft Corporation and Contributors. +// Licensed under the MIT License. + +#pragma once + +#include "M.W.S.ApplicationDataContainer.h" + +namespace Microsoft::Windows::Storage +{ + struct UnpackagedApplicationData + { + UnpackagedApplicationData() = default; + UnpackagedApplicationData(winrt::hstring const& publisher, winrt::hstring const& product); + + bool IsMachinePathSupported(); + winrt::hstring LocalCachePath(); + winrt::hstring LocalPath(); + winrt::hstring MachinePath(); + winrt::hstring SharedLocalPath(); + winrt::hstring TemporaryPath(); + winrt::Windows::Storage::StorageFolder LocalCacheFolder(); + winrt::Windows::Storage::StorageFolder LocalFolder(); + winrt::Windows::Storage::StorageFolder MachineFolder(); + winrt::Windows::Storage::StorageFolder SharedLocalFolder(); + winrt::Windows::Storage::StorageFolder TemporaryFolder(); + winrt::Microsoft::Windows::Storage::ApplicationDataContainer LocalSettings(); + winrt::Windows::Foundation::IAsyncAction ClearAsync(winrt::Microsoft::Windows::Storage::ApplicationDataLocality locality); + winrt::Windows::Foundation::IAsyncAction ClearPublisherCacheFolderAsync(winrt::hstring folderName); + void Close(); + winrt::hstring GetPublisherCachePath(winrt::hstring const& folderName); + winrt::Windows::Storage::StorageFolder GetPublisherCacheFolder(winrt::hstring const& folderName); + + private: + winrt::Windows::Foundation::IAsyncAction ClearMachineFolderAsync(); + static std::filesystem::path _MachinePath(winrt::hstring const& publisher, winrt::hstring const& product); + static bool _PathExists(std::filesystem::path const& path); + + private: + winrt::hstring m_publisher; + winrt::hstring m_product; + winrt::hstring m_localPath; + std::filesystem::path m_machinePath; + }; +} From 609292bcd7048e9ff7a727d5e3cfb229090d750a Mon Sep 17 00:00:00 2001 From: Howard Kapustein Date: Fri, 29 Nov 2024 12:32:21 -0800 Subject: [PATCH 02/24] Integrated UnpackagedApplicationData into ApplicationData --- dev/ApplicationData/M.W.S.ApplicationData.cpp | 26 +++++++++++++++---- dev/ApplicationData/M.W.S.ApplicationData.h | 4 ++- 2 files changed, 24 insertions(+), 6 deletions(-) diff --git a/dev/ApplicationData/M.W.S.ApplicationData.cpp b/dev/ApplicationData/M.W.S.ApplicationData.cpp index 26a31d2400..234846deee 100644 --- a/dev/ApplicationData/M.W.S.ApplicationData.cpp +++ b/dev/ApplicationData/M.W.S.ApplicationData.cpp @@ -21,11 +21,26 @@ static_assert(static_cast(winrt::Microsoft::Windows::Storage::Applicati namespace winrt::Microsoft::Windows::Storage::implementation { - ApplicationData::ApplicationData(winrt::Windows::Storage::ApplicationData const& value, hstring const& packageFamilyName) : + ApplicationData::ApplicationData(hstring const& packageFamilyName) : + m_unpackagedApplicationData(), + m_applicationData(nullptr), + m_packageFamilyName(packageFamilyName) + { + } + ApplicationData::ApplicationData(winrt::Windows::Storage::ApplicationData& value, hstring const& packageFamilyName) : + m_unpackagedApplicationData(), m_applicationData(value), m_packageFamilyName(packageFamilyName) { } + ApplicationData::ApplicationData(hstring const& publisher, hstring const& product) : + m_unpackagedApplicationData(), + m_applicationData(nullptr), + m_packageFamilyName() + { + auto unpackagedApplicationData{ new ::Microsoft::Windows::Storage::UnpackagedApplicationData(publisher, product) }; + m_unpackagedApplicationData.reset(unpackagedApplicationData); + } winrt::Microsoft::Windows::Storage::ApplicationData ApplicationData::GetDefault() { const auto packageFamilyName{ ::AppModel::Identity::GetCurrentPackageFamilyName() }; @@ -84,7 +99,7 @@ namespace winrt::Microsoft::Windows::Storage::implementation { // The package family has package(s) registered for the user with at least 1 Framework package // (and a package family can't have Framework and not-Framework packages in the same package family) - return winrt::make(nullptr, packageFamilyName); + return winrt::make(packageFamilyName); } } @@ -92,10 +107,11 @@ namespace winrt::Microsoft::Windows::Storage::implementation throw; } } - winrt::Microsoft::Windows::Storage::ApplicationData ApplicationData::GetForUnpackaged(hstring const& /*publisher*/, hstring const& /*product*/) + winrt::Microsoft::Windows::Storage::ApplicationData ApplicationData::GetForUnpackaged(hstring const& publisher, hstring const& product) { - // TODO implement GetForUnpackaged - throw hresult_not_implemented(); + THROW_HR_IF_MSG(E_INVALIDARG, publisher.empty(), "Publisher not valid"); + THROW_HR_IF_MSG(E_INVALIDARG, product.empty(), "Product not valid"); + return winrt::make(publisher, product); } bool ApplicationData::IsMachinePathSupported() { diff --git a/dev/ApplicationData/M.W.S.ApplicationData.h b/dev/ApplicationData/M.W.S.ApplicationData.h index c985a883c1..8047cb550e 100644 --- a/dev/ApplicationData/M.W.S.ApplicationData.h +++ b/dev/ApplicationData/M.W.S.ApplicationData.h @@ -12,7 +12,9 @@ namespace winrt::Microsoft::Windows::Storage::implementation struct ApplicationData : ApplicationDataT { ApplicationData() = default; - ApplicationData(winrt::Windows::Storage::ApplicationData const& value, hstring const& packageFamilyName); + ApplicationData(hstring const& packageFamilyName); + ApplicationData(winrt::Windows::Storage::ApplicationData& value, hstring const& packageFamilyName); + ApplicationData(hstring const& publisher, hstring const& product); static winrt::Microsoft::Windows::Storage::ApplicationData GetDefault(); static winrt::Microsoft::Windows::Storage::ApplicationData GetForUser(winrt::Windows::System::User user); From 16715d323df6b4268cb812bd3809e99bd382f403 Mon Sep 17 00:00:00 2001 From: Howard Kapustein Date: Fri, 29 Nov 2024 18:07:41 -0800 Subject: [PATCH 03/24] Added tests. Removed debugging and dead code --- .../UnpackagedApplicationData.cpp | 22 +- test/ApplicationData/ApplicationDataTests.cpp | 8 - .../ApplicationDataTests.vcxproj | 2 +- .../ApplicationDataTests.vcxproj.filters | 2 +- .../ApplicationDataTests_Module.cpp | 8 - .../UnpackagedApplicationDataTests.cpp | 407 ++++++++++++++++++ 6 files changed, 422 insertions(+), 27 deletions(-) delete mode 100644 test/ApplicationData/ApplicationDataTests_Module.cpp create mode 100644 test/ApplicationData/UnpackagedApplicationDataTests.cpp diff --git a/dev/ApplicationData/UnpackagedApplicationData.cpp b/dev/ApplicationData/UnpackagedApplicationData.cpp index 9975bfe0df..9f2bf0f702 100644 --- a/dev/ApplicationData/UnpackagedApplicationData.cpp +++ b/dev/ApplicationData/UnpackagedApplicationData.cpp @@ -111,20 +111,24 @@ namespace Microsoft::Windows::Storage } std::filesystem::path UnpackagedApplicationData::_MachinePath(winrt::hstring const& publisher, winrt::hstring const& product) { - std::filesystem::path path; - if (path.empty()) + // Path = %ProgramData%\...publisher...\...product... + wil::unique_cotaskmem_string programData; + THROW_IF_FAILED(SHGetKnownFolderPath(FOLDERID_AppDataProgramData, KF_FLAG_DEFAULT /*TODO KF_FLAG_CREATE | KF_FLAG_DONT_VERIFY*/, nullptr, wil::out_param(programData))); + std::filesystem::path path{ programData.get() }; + path /= publisher.c_str(); + path /= product.c_str(); + + // Does it exist? + if (_PathExists(path)) { - wil::unique_cotaskmem_string programData; - THROW_IF_FAILED(SHGetKnownFolderPath(FOLDERID_AppDataProgramData, KF_FLAG_DEFAULT /*TODO KF_FLAG_CREATE | KF_FLAG_DONT_VERIFY*/, nullptr, wil::out_param(programData))); - path = programData.get(); - path /= publisher.c_str(); - path /= product.c_str(); + return path; } - return path; + return std::filesystem::path{}; } bool UnpackagedApplicationData::_PathExists(std::filesystem::path const& path) { - throw winrt::hresult_not_implemented(); + const std::filesystem::directory_entry directoryEntry{ path }; + return directoryEntry.is_directory(); } } diff --git a/test/ApplicationData/ApplicationDataTests.cpp b/test/ApplicationData/ApplicationDataTests.cpp index 06a0b273c1..e48649be5e 100644 --- a/test/ApplicationData/ApplicationDataTests.cpp +++ b/test/ApplicationData/ApplicationDataTests.cpp @@ -239,23 +239,15 @@ namespace Test::ApplicationData::Tests TEST_METHOD(MachineFolderAndPath_Main_NotSupported) { winrt::hstring packageFamilyName{ Main_PackageFamilyName }; -WEX::Logging::Log::Comment(WEX::Common::String().Format(L"A1: %ls", packageFamilyName.c_str())); auto applicationData{ winrt::Microsoft::Windows::Storage::ApplicationData::GetForPackageFamily(packageFamilyName) }; -WEX::Logging::Log::Comment(WEX::Common::String().Format(L"A2: %ls", packageFamilyName.c_str())); VERIFY_IS_NOT_NULL(applicationData); -WEX::Logging::Log::Comment(WEX::Common::String().Format(L"A3: %ls", packageFamilyName.c_str())); VERIFY_IS_FALSE(applicationData.IsMachinePathSupported()); -WEX::Logging::Log::Comment(WEX::Common::String().Format(L"A4: %ls", packageFamilyName.c_str())); const auto machineFolder{ applicationData.MachineFolder() }; -WEX::Logging::Log::Comment(WEX::Common::String().Format(L"A5: %ls", packageFamilyName.c_str())); VERIFY_IS_NULL(machineFolder); -WEX::Logging::Log::Comment(WEX::Common::String().Format(L"A6: %ls", packageFamilyName.c_str())); const auto machinePath{ applicationData.MachinePath() }; -WEX::Logging::Log::Comment(WEX::Common::String().Format(L"A7: %ls", packageFamilyName.c_str())); VERIFY_ARE_EQUAL(machinePath, null_hstring); -WEX::Logging::Log::Comment(WEX::Common::String().Format(L"A8: %ls", packageFamilyName.c_str())); } TEST_METHOD(MachineFolderAndPath_Framework_NotSupported) diff --git a/test/ApplicationData/ApplicationDataTests.vcxproj b/test/ApplicationData/ApplicationDataTests.vcxproj index fbe4a19a66..56fc01e572 100644 --- a/test/ApplicationData/ApplicationDataTests.vcxproj +++ b/test/ApplicationData/ApplicationDataTests.vcxproj @@ -111,7 +111,7 @@ - + diff --git a/test/ApplicationData/ApplicationDataTests.vcxproj.filters b/test/ApplicationData/ApplicationDataTests.vcxproj.filters index 393dcf3001..fc2384bff0 100644 --- a/test/ApplicationData/ApplicationDataTests.vcxproj.filters +++ b/test/ApplicationData/ApplicationDataTests.vcxproj.filters @@ -24,7 +24,7 @@ Source Files - + Source Files diff --git a/test/ApplicationData/ApplicationDataTests_Module.cpp b/test/ApplicationData/ApplicationDataTests_Module.cpp deleted file mode 100644 index d9df50685a..0000000000 --- a/test/ApplicationData/ApplicationDataTests_Module.cpp +++ /dev/null @@ -1,8 +0,0 @@ -// Copyright (c) Microsoft Corporation and Contributors. -// Licensed under the MIT License. - -#include "pch.h" - -//BEGIN_MODULE() -// MODULE_PROPERTY(L"IsolationLevel", L"Class") -//END_MODULE() diff --git a/test/ApplicationData/UnpackagedApplicationDataTests.cpp b/test/ApplicationData/UnpackagedApplicationDataTests.cpp new file mode 100644 index 0000000000..37ac032b04 --- /dev/null +++ b/test/ApplicationData/UnpackagedApplicationDataTests.cpp @@ -0,0 +1,407 @@ +// Copyright (c) Microsoft Corporation and Contributors. +// Licensed under the MIT License. + +#include "pch.h" + +#include + +namespace TD = ::Test::Diagnostics; +namespace TB = ::Test::Bootstrap; +namespace TP = ::Test::Packages; +namespace TD = ::Test::Diagnostics; + +static const winrt::hstring null_hstring; + +namespace Test::ApplicationData::Tests +{ + const winrt::hstring Publisher{ L"ApplicationDataTests" }; + const winrt::hstring Product{ L"UnpackagedApplicationDataTests" }; + + class UnpackagedApplicationDataTests + { + public: + BEGIN_TEST_CLASS(UnpackagedApplicationDataTests) + TEST_CLASS_PROPERTY(L"ThreadingModel", L"MTA") + TEST_CLASS_PROPERTY(L"IsolationLevel", L"Class") + TEST_CLASS_PROPERTY(L"RunAs", L"RestrictedUser") + END_TEST_CLASS() + + TEST_CLASS_SETUP(ClassSetup) + { + ::TD::DumpExecutionContext(); + if (!::WindowsVersion::IsWindows11_21H2OrGreater()) + { + WEX::Logging::Log::Result(WEX::Logging::TestResults::Skipped, L"ApplicationData requires Win11 >= 21H2 (SV1). Skipping tests"); + return true; + } + + ::TD::DumpExecutionContext(); + + ::TB::Setup(); + CreateResources(); + return true; + } + + TEST_CLASS_CLEANUP(ClassCleanup) + { + DeleteResources(); + ::TB::Cleanup(); + return true; + } + + void CreateResources() + { + const auto localPath{ UserLocalPath(Publisher, Product) }; + VERIFY_SUCCEEDED(wil::CreateDirectoryDeepNoThrow(localPath.c_str()), + WEX::Common::String().Format(L"MKDIR %s", localPath.c_str())); + + const auto temporaryPath{ UserLocalPath(Publisher, Product) }; + VERIFY_SUCCEEDED(wil::CreateDirectoryDeepNoThrow(temporaryPath.c_str()), + WEX::Common::String().Format(L"MKDIR %s", temporaryPath.c_str())); + } + + void DeleteResources() + { + const auto localPath{ UserLocalPath(Publisher, Product) }; + const auto removeOptions{ wil::RemoveDirectoryOptions::RemoveReadOnly }; + VERIFY_SUCCEEDED(wil::RemoveDirectoryRecursiveNoThrow(localPath.c_str(), removeOptions), + WEX::Common::String().Format(L"RMDIR %s", localPath.c_str())); + + const auto temporaryPath{ UserLocalPath(Publisher, Product) }; + VERIFY_SUCCEEDED(wil::RemoveDirectoryRecursiveNoThrow(temporaryPath.c_str(), removeOptions), + WEX::Common::String().Format(L"RMDIR %s", temporaryPath.c_str())); + } + + TEST_METHOD(GetForUnpackaged_InvalidParameter) + { + const winrt::hstring publisher{ L"FabrikamTest" }; + const winrt::hstring product{ L"ApplicationDataTest" }; + const winrt::hstring emptyString; + + try + { + [[maybe_unused]] auto unpackagedApplicationData{ winrt::Microsoft::Windows::Storage::ApplicationData::GetForUnpackaged(emptyString, Product) }; + VERIFY_FAIL(L"Success is not expected"); + } + catch (winrt::hresult_error& e) + { + VERIFY_ARE_EQUAL(E_INVALIDARG, e.code(), WEX::Common::String().Format(L"0x%X %s", e.code(), e.message().c_str())); + } + + try + { + [[maybe_unused]] auto unpackagedApplicationData{ winrt::Microsoft::Windows::Storage::ApplicationData::GetForUnpackaged(Publisher, emptyString) }; + VERIFY_FAIL(L"Success is not expected"); + } + catch (winrt::hresult_error& e) + { + VERIFY_ARE_EQUAL(E_INVALIDARG, e.code(), WEX::Common::String().Format(L"0x%X %s", e.code(), e.message().c_str())); + } + } + + TEST_METHOD(GetForUnpackaged) + { + auto applicationData{ winrt::Microsoft::Windows::Storage::ApplicationData::GetForUnpackaged(Publisher, Product) }; + VERIFY_IS_NOT_NULL(applicationData); + } + + winrt::hstring UserLocalPath(winrt::hstring const& publisher, winrt::hstring const& product) + { + // %LOCALAPPDATA%\...publisher...\...product... + wil::unique_cotaskmem_string localAppData; + //TODO FOLDERID_LocalAppDataLow + VERIFY_SUCCEEDED(SHGetKnownFolderPath(FOLDERID_LocalAppData, KF_FLAG_DEFAULT /*TODO KF_FLAG_CREATE | KF_FLAG_DONT_VERIFY*/, nullptr, wil::out_param(localAppData))); + std::filesystem::path path{ localAppData.get() }; + path /= publisher.c_str(); + path /= product.c_str(); + return winrt::hstring{ path.c_str() }; + } + + winrt::Windows::Storage::StorageFolder UserLocalFolder(winrt::hstring const& publisher, winrt::hstring const& product) + { + const auto path{ UserLocalPath(publisher, product) }; + return winrt::Windows::Storage::StorageFolder::GetFolderFromPathAsync(path).get(); + } + + winrt::hstring UserTemporaryPath(winrt::hstring const& publisher, winrt::hstring const& product) + { + // %LOCALAPPDATA%\...publisher...\...product... + wil::unique_cotaskmem_string localAppData; + //TODO FOLDERID_LocalAppDataLow + VERIFY_SUCCEEDED(SHGetKnownFolderPath(FOLDERID_LocalAppData, KF_FLAG_DEFAULT /*TODO KF_FLAG_CREATE | KF_FLAG_DONT_VERIFY*/, nullptr, wil::out_param(localAppData))); + std::filesystem::path path{ localAppData.get() }; + path /= publisher.c_str(); + path /= product.c_str(); + return winrt::hstring{ path.c_str() }; + } + + winrt::Windows::Storage::StorageFolder UserTemporaryFolder(winrt::hstring const& publisher, winrt::hstring const& product) + { + const auto path{ UserTemporaryPath(publisher, product) }; + return winrt::Windows::Storage::StorageFolder::GetFolderFromPathAsync(path).get(); + } + + TEST_METHOD(FolderAndPath) + { + auto applicationData{ winrt::Microsoft::Windows::Storage::ApplicationData::GetForUnpackaged(Publisher, Product) }; + VERIFY_IS_NOT_NULL(applicationData); + + try + { + [[maybe_unused]] auto path{ applicationData.LocalCachePath() }; + VERIFY_FAIL(L"Success is not expected"); + } + catch (winrt::hresult_error& e) + { + VERIFY_ARE_EQUAL(E_NOTIMPL, e.code(), WEX::Common::String().Format(L"0x%X %s", e.code(), e.message().c_str())); + } + + try + { + [[maybe_unused]] auto folder{ applicationData.LocalCacheFolder() }; + VERIFY_FAIL(L"Success is not expected"); + } + catch (winrt::hresult_error& e) + { + VERIFY_ARE_EQUAL(E_NOTIMPL, e.code(), WEX::Common::String().Format(L"0x%X %s", e.code(), e.message().c_str())); + } + + const auto localFolder{ applicationData.LocalFolder() }; + const auto localPath{ applicationData.LocalPath() }; + VERIFY_ARE_EQUAL(localFolder.Path(), localPath); + const auto expectedLocalFolder{ UserLocalFolder(Publisher, Product) }; + const auto expectedLocalPath{ UserLocalPath(Publisher, Product) }; + VERIFY_ARE_EQUAL(localPath, expectedLocalPath); + + try + { + [[maybe_unused]] auto path{ applicationData.SharedLocalPath() }; + VERIFY_FAIL(L"Success is not expected"); + } + catch (winrt::hresult_error& e) + { + VERIFY_ARE_EQUAL(E_NOTIMPL, e.code(), WEX::Common::String().Format(L"0x%X %s", e.code(), e.message().c_str())); + } + + try + { + [[maybe_unused]] auto folder{ applicationData.SharedLocalFolder() }; + VERIFY_FAIL(L"Success is not expected"); + } + catch (winrt::hresult_error& e) + { + VERIFY_ARE_EQUAL(E_NOTIMPL, e.code(), WEX::Common::String().Format(L"0x%X %s", e.code(), e.message().c_str())); + } + + const auto temporaryFolder{ applicationData.TemporaryFolder() }; + const auto temporaryPath{ applicationData.TemporaryPath() }; + VERIFY_ARE_EQUAL(temporaryFolder.Path(), temporaryPath); + const auto expectedTemporaryFolder{ UserTemporaryFolder(Publisher, Product) }; + const auto expectedTemporaryPath{ UserTemporaryPath(Publisher, Product) }; + VERIFY_ARE_EQUAL(temporaryPath, expectedTemporaryPath); + } + + TEST_METHOD(PublisherCacheFolderAndPath) + { + auto applicationData{ winrt::Microsoft::Windows::Storage::ApplicationData::GetForUnpackaged(Publisher, Product) }; + VERIFY_IS_NOT_NULL(applicationData); + + winrt::hstring folderName{ L"Does.Not.Exist" }; + try + { + [[maybe_unused]] auto path{ applicationData.GetPublisherCachePath(folderName) }; + VERIFY_FAIL(L"Success is not expected"); + } + catch (winrt::hresult_error& e) + { + VERIFY_ARE_EQUAL(E_NOTIMPL, e.code(), WEX::Common::String().Format(L"0x%X %s", e.code(), e.message().c_str())); + } + + try + { + [[maybe_unused]] auto folder{ applicationData.GetPublisherCacheFolder(folderName) }; + VERIFY_FAIL(L"Success is not expected"); + } + catch (winrt::hresult_error& e) + { + VERIFY_ARE_EQUAL(E_NOTIMPL, e.code(), WEX::Common::String().Format(L"0x%X %s", e.code(), e.message().c_str())); + } + } + + TEST_METHOD(MachineFolderAndPath_NotSupported) + { + auto applicationData{ winrt::Microsoft::Windows::Storage::ApplicationData::GetForUnpackaged(Publisher, Product) }; + VERIFY_IS_NOT_NULL(applicationData); + + VERIFY_IS_FALSE(applicationData.IsMachinePathSupported()); + + const auto machineFolder{ applicationData.MachineFolder() }; + VERIFY_IS_NULL(machineFolder); + const auto machinePath{ applicationData.MachinePath() }; + VERIFY_ARE_EQUAL(machinePath, null_hstring); + } + + TEST_METHOD(LocalSettings) + { +#ifdef TODO_LocalSettings + winrt::hstring packageFamilyName{ Main_PackageFamilyName }; + auto applicationData{ winrt::Microsoft::Windows::Storage::ApplicationData::GetForUnpackaged(Publisher, Product) }; + VERIFY_IS_NOT_NULL(applicationData); + + auto systemApplicationData{ winrt::Windows::Management::Core::ApplicationDataManager::CreateForPackageFamily(packageFamilyName) }; + VERIFY_IS_NOT_NULL(systemApplicationData); + + const auto localSettings{ applicationData.LocalSettings() }; + const auto systemLocalSettings{ systemApplicationData.LocalSettings() }; + VERIFY_ARE_EQUAL(static_cast(localSettings.Locality()), static_cast(systemLocalSettings.Locality())); + + auto containers{ localSettings.Containers() }; + VERIFY_ARE_EQUAL(0u, containers.Size()); + auto systemContainers{ systemLocalSettings.Containers() }; + VERIFY_ARE_EQUAL(0u, systemContainers.Size()); + VERIFY_ARE_EQUAL(containers.Size(), systemContainers.Size()); + + const winrt::hstring foodAndStuff{ L"FoodAndStuff" }; + VERIFY_IS_FALSE(containers.HasKey(foodAndStuff)); + VERIFY_IS_FALSE(systemContainers.HasKey(foodAndStuff)); + + auto container{ localSettings.CreateContainer(foodAndStuff, winrt::Microsoft::Windows::Storage::ApplicationDataCreateDisposition::Always) }; + VERIFY_ARE_EQUAL(foodAndStuff, container.Name()); + // + VERIFY_ARE_EQUAL(0u, containers.Size()); + VERIFY_IS_FALSE(containers.HasKey(foodAndStuff)); + containers = localSettings.Containers(); + VERIFY_ARE_EQUAL(1u, containers.Size()); + VERIFY_IS_TRUE(containers.HasKey(foodAndStuff)); + container = containers.Lookup(foodAndStuff); + VERIFY_IS_NOT_NULL(container); + VERIFY_ARE_EQUAL(foodAndStuff, container.Name()); + // + VERIFY_ARE_EQUAL(0u, systemContainers.Size()); + VERIFY_IS_FALSE(systemContainers.HasKey(foodAndStuff)); + systemContainers = systemLocalSettings.Containers(); + VERIFY_ARE_EQUAL(1u, systemContainers.Size()); + VERIFY_IS_TRUE(systemContainers.HasKey(foodAndStuff)); + auto systemContainer{ systemContainers.Lookup(foodAndStuff) }; + VERIFY_IS_NOT_NULL(systemContainer); + VERIFY_ARE_EQUAL(foodAndStuff, systemContainer.Name()); + + const winrt::hstring keyMeat{ L"Meat" }; + const winrt::hstring rawValueSteak{ L"Steak" }; + auto valueSteak{ winrt::Windows::Foundation::PropertyValue::CreateString(rawValueSteak) }; + auto values{ container.Values() }; + VERIFY_ARE_EQUAL(0u, values.Size()); + values.Insert(keyMeat, valueSteak); + VERIFY_ARE_EQUAL(1u, values.Size()); + auto steak{ values.Lookup(keyMeat) }; + VERIFY_IS_NOT_NULL(steak); + auto steakLookupAsReferenceString{ steak.try_as>() }; + VERIFY_IS_NOT_NULL(steakLookupAsReferenceString); + auto steakString{ steakLookupAsReferenceString.GetString() }; + VERIFY_ARE_EQUAL(rawValueSteak, steakString); + // + auto systemValues{ systemContainer.Values() }; + VERIFY_ARE_EQUAL(1u, systemValues.Size()); + auto systemSteak{ systemValues.Lookup(keyMeat) }; + VERIFY_IS_NOT_NULL(systemSteak); + auto systemSteakLookupAsReferenceString{ systemSteak.try_as>() }; + VERIFY_IS_NOT_NULL(systemSteakLookupAsReferenceString); + auto systemSteakString{ systemSteakLookupAsReferenceString.GetString() }; + VERIFY_ARE_EQUAL(rawValueSteak, systemSteakString); + + const winrt::hstring keyDrink{ L"Drink" }; + const winrt::hstring rawValueWhiskey{ L"Whiskey" }; + auto valueWhiskey{ winrt::Windows::Foundation::PropertyValue::CreateString(rawValueWhiskey) }; + VERIFY_ARE_EQUAL(1u, systemValues.Size()); + systemValues.Insert(keyDrink, valueWhiskey); + VERIFY_ARE_EQUAL(2u, systemValues.Size()); + auto systemWhiskey{ systemValues.Lookup(keyDrink) }; + VERIFY_IS_NOT_NULL(systemWhiskey); + auto systemWhiskeyLookupAsReferenceString{ systemWhiskey.try_as>() }; + VERIFY_IS_NOT_NULL(systemWhiskeyLookupAsReferenceString); + auto systemWhiskeyString{ systemWhiskeyLookupAsReferenceString.GetString() }; + VERIFY_ARE_EQUAL(rawValueWhiskey, systemWhiskeyString); + // + VERIFY_ARE_EQUAL(2u, values.Size()); + auto whiskey{ values.Lookup(keyDrink) }; + VERIFY_IS_NOT_NULL(whiskey); + auto whiskeyLookupAsReferenceString{ whiskey.try_as>() }; + VERIFY_IS_NOT_NULL(whiskeyLookupAsReferenceString); + auto whiskeyString{ whiskeyLookupAsReferenceString.GetString() }; + VERIFY_ARE_EQUAL(rawValueWhiskey, whiskeyString); + + VERIFY_ARE_EQUAL(winrt::Microsoft::Windows::Storage::ApplicationDataLocality::Local, container.Locality()); + container.Close(); + try + { + [[maybe_unused]] auto locality{ container.Locality() }; + VERIFY_FAIL(L"Success is not expected"); + } + catch (winrt::hresult_error& e) + { + VERIFY_ARE_EQUAL(RO_E_CLOSED, e.code(), WEX::Common::String().Format(L"0x%X %s", e.code(), e.message().c_str())); + } + VERIFY_ARE_EQUAL(winrt::Windows::Storage::ApplicationDataLocality::Local, systemContainer.Locality()); + systemContainer.Close(); + try + { + [[maybe_unused]] auto locality{ systemContainer.Locality() }; + VERIFY_FAIL(L"Success is not expected"); + } + catch (winrt::hresult_error& e) + { + VERIFY_ARE_EQUAL(RO_E_CLOSED, e.code(), WEX::Common::String().Format(L"0x%X %s", e.code(), e.message().c_str())); + } + + VERIFY_ARE_EQUAL(1u, localSettings.Containers().Size()); + VERIFY_ARE_EQUAL(1u, systemLocalSettings.Containers().Size()); + localSettings.DeleteContainer(foodAndStuff); + VERIFY_ARE_EQUAL(0u, localSettings.Containers().Size()); + VERIFY_ARE_EQUAL(0u, systemLocalSettings.Containers().Size()); + + VERIFY_ARE_EQUAL(winrt::Microsoft::Windows::Storage::ApplicationDataLocality::Local, localSettings.Locality()); + localSettings.Close(); + try + { + [[maybe_unused]] auto locality{ localSettings.Locality() }; + VERIFY_FAIL(L"Success is not expected"); + } + catch (winrt::hresult_error& e) + { + VERIFY_ARE_EQUAL(RO_E_CLOSED, e.code(), WEX::Common::String().Format(L"0x%X %s", e.code(), e.message().c_str())); + } + VERIFY_ARE_EQUAL(winrt::Windows::Storage::ApplicationDataLocality::Local, systemLocalSettings.Locality()); + systemLocalSettings.Close(); + try + { + [[maybe_unused]] auto locality{ systemLocalSettings.Locality() }; + VERIFY_FAIL(L"Success is not expected"); + } + catch (winrt::hresult_error& e) + { + VERIFY_ARE_EQUAL(RO_E_CLOSED, e.code(), WEX::Common::String().Format(L"0x%X %s", e.code(), e.message().c_str())); + } +#endif + } + + TEST_METHOD(ClearAsync) + { + //TODO + } + + TEST_METHOD(ClearFolderAsync_Machine) + { + //TODO + } + + TEST_METHOD(ClearPublisherCacheFolderAsync) + { + auto applicationData{ winrt::Microsoft::Windows::Storage::ApplicationData::GetForUnpackaged(Publisher, Product) }; + VERIFY_IS_NOT_NULL(applicationData); + winrt::hstring folderName{ L"Does.Not.Exist" }; + [[maybe_unused]] auto asyncAction{ applicationData.ClearPublisherCacheFolderAsync(folderName) }; + VERIFY_ARE_EQUAL(winrt::Windows::Foundation::AsyncStatus::Error, asyncAction.Status()); + VERIFY_ARE_EQUAL(E_NOTIMPL, asyncAction.ErrorCode()); + } + }; +} From 269b3351e5730e6ebe5c4f660648ee7adb289f86 Mon Sep 17 00:00:00 2001 From: Howard Kapustein Date: Fri, 29 Nov 2024 18:42:00 -0800 Subject: [PATCH 04/24] Fixed most tests --- .../UnpackagedApplicationData.cpp | 11 ++- .../UnpackagedApplicationData.h | 1 + .../UnpackagedApplicationDataTests.cpp | 72 +++++++++++++++---- 3 files changed, 68 insertions(+), 16 deletions(-) diff --git a/dev/ApplicationData/UnpackagedApplicationData.cpp b/dev/ApplicationData/UnpackagedApplicationData.cpp index 9f2bf0f702..24fe8879b6 100644 --- a/dev/ApplicationData/UnpackagedApplicationData.cpp +++ b/dev/ApplicationData/UnpackagedApplicationData.cpp @@ -56,7 +56,16 @@ namespace Microsoft::Windows::Storage } winrt::hstring UnpackagedApplicationData::TemporaryPath() { - throw winrt::hresult_not_implemented(); + if (m_temporaryPath.empty()) + { + //TODO LocalSystem + // GetTempPath() + \...publisher...\...product... + auto path{ std::filesystem::temp_directory_path() }; + path /= m_publisher.c_str(); + path /= m_product.c_str(); + m_temporaryPath = path.c_str(); + } + return m_temporaryPath; } winrt::Windows::Storage::StorageFolder UnpackagedApplicationData::LocalCacheFolder() { diff --git a/dev/ApplicationData/UnpackagedApplicationData.h b/dev/ApplicationData/UnpackagedApplicationData.h index b810303e06..677448de98 100644 --- a/dev/ApplicationData/UnpackagedApplicationData.h +++ b/dev/ApplicationData/UnpackagedApplicationData.h @@ -40,5 +40,6 @@ namespace Microsoft::Windows::Storage winrt::hstring m_product; winrt::hstring m_localPath; std::filesystem::path m_machinePath; + winrt::hstring m_temporaryPath; }; } diff --git a/test/ApplicationData/UnpackagedApplicationDataTests.cpp b/test/ApplicationData/UnpackagedApplicationDataTests.cpp index 37ac032b04..c0c0879311 100644 --- a/test/ApplicationData/UnpackagedApplicationDataTests.cpp +++ b/test/ApplicationData/UnpackagedApplicationDataTests.cpp @@ -55,21 +55,28 @@ namespace Test::ApplicationData::Tests VERIFY_SUCCEEDED(wil::CreateDirectoryDeepNoThrow(localPath.c_str()), WEX::Common::String().Format(L"MKDIR %s", localPath.c_str())); - const auto temporaryPath{ UserLocalPath(Publisher, Product) }; + const auto temporaryPath{ UserTemporaryPath(Publisher, Product) }; VERIFY_SUCCEEDED(wil::CreateDirectoryDeepNoThrow(temporaryPath.c_str()), WEX::Common::String().Format(L"MKDIR %s", temporaryPath.c_str())); } void DeleteResources() { - const auto localPath{ UserLocalPath(Publisher, Product) }; - const auto removeOptions{ wil::RemoveDirectoryOptions::RemoveReadOnly }; - VERIFY_SUCCEEDED(wil::RemoveDirectoryRecursiveNoThrow(localPath.c_str(), removeOptions), - WEX::Common::String().Format(L"RMDIR %s", localPath.c_str())); + const std::filesystem::path temporaryPath{ UserTemporaryPath(Publisher).c_str() }; + if (PathExists(temporaryPath)) + { + const auto removeOptions{ wil::RemoveDirectoryOptions::RemoveReadOnly }; + VERIFY_SUCCEEDED(wil::RemoveDirectoryRecursiveNoThrow(temporaryPath.c_str(), removeOptions), + WEX::Common::String().Format(L"RMDIR %s", temporaryPath.c_str())); + } - const auto temporaryPath{ UserLocalPath(Publisher, Product) }; - VERIFY_SUCCEEDED(wil::RemoveDirectoryRecursiveNoThrow(temporaryPath.c_str(), removeOptions), - WEX::Common::String().Format(L"RMDIR %s", temporaryPath.c_str())); + const std::filesystem::path localPath{ UserLocalPath(Publisher).c_str() }; + if (PathExists(localPath)) + { + const auto removeOptions{ wil::RemoveDirectoryOptions::RemoveReadOnly }; + VERIFY_SUCCEEDED(wil::RemoveDirectoryRecursiveNoThrow(localPath.c_str(), removeOptions), + WEX::Common::String().Format(L"RMDIR %s", localPath.c_str())); + } } TEST_METHOD(GetForUnpackaged_InvalidParameter) @@ -105,7 +112,13 @@ namespace Test::ApplicationData::Tests VERIFY_IS_NOT_NULL(applicationData); } - winrt::hstring UserLocalPath(winrt::hstring const& publisher, winrt::hstring const& product) + static bool PathExists(std::filesystem::path const& path) + { + const std::filesystem::directory_entry directoryEntry{ path }; + return directoryEntry.is_directory(); + } + + std::filesystem::path UserLocalPath(winrt::hstring const& publisher) { // %LOCALAPPDATA%\...publisher...\...product... wil::unique_cotaskmem_string localAppData; @@ -113,6 +126,12 @@ namespace Test::ApplicationData::Tests VERIFY_SUCCEEDED(SHGetKnownFolderPath(FOLDERID_LocalAppData, KF_FLAG_DEFAULT /*TODO KF_FLAG_CREATE | KF_FLAG_DONT_VERIFY*/, nullptr, wil::out_param(localAppData))); std::filesystem::path path{ localAppData.get() }; path /= publisher.c_str(); + return path; + } + + winrt::hstring UserLocalPath(winrt::hstring const& publisher, winrt::hstring const& product) + { + auto path{ UserLocalPath(publisher) }; path /= product.c_str(); return winrt::hstring{ path.c_str() }; } @@ -123,14 +142,19 @@ namespace Test::ApplicationData::Tests return winrt::Windows::Storage::StorageFolder::GetFolderFromPathAsync(path).get(); } + std::filesystem::path UserTemporaryPath(winrt::hstring const& publisher) + { + //TODO LocalSystem + // GetTempPath() + \...publisher...\...product... + auto path{ std::filesystem::temp_directory_path() }; + path /= publisher.c_str(); + return path; + } + winrt::hstring UserTemporaryPath(winrt::hstring const& publisher, winrt::hstring const& product) { // %LOCALAPPDATA%\...publisher...\...product... - wil::unique_cotaskmem_string localAppData; - //TODO FOLDERID_LocalAppDataLow - VERIFY_SUCCEEDED(SHGetKnownFolderPath(FOLDERID_LocalAppData, KF_FLAG_DEFAULT /*TODO KF_FLAG_CREATE | KF_FLAG_DONT_VERIFY*/, nullptr, wil::out_param(localAppData))); - std::filesystem::path path{ localAppData.get() }; - path /= publisher.c_str(); + auto path{ UserTemporaryPath(publisher) }; path /= product.c_str(); return winrt::hstring{ path.c_str() }; } @@ -141,7 +165,7 @@ namespace Test::ApplicationData::Tests return winrt::Windows::Storage::StorageFolder::GetFolderFromPathAsync(path).get(); } - TEST_METHOD(FolderAndPath) + TEST_METHOD(LocalCacheFolderAndPath) { auto applicationData{ winrt::Microsoft::Windows::Storage::ApplicationData::GetForUnpackaged(Publisher, Product) }; VERIFY_IS_NOT_NULL(applicationData); @@ -165,6 +189,12 @@ namespace Test::ApplicationData::Tests { VERIFY_ARE_EQUAL(E_NOTIMPL, e.code(), WEX::Common::String().Format(L"0x%X %s", e.code(), e.message().c_str())); } + } + + TEST_METHOD(LocalFolderAndPath) + { + auto applicationData{ winrt::Microsoft::Windows::Storage::ApplicationData::GetForUnpackaged(Publisher, Product) }; + VERIFY_IS_NOT_NULL(applicationData); const auto localFolder{ applicationData.LocalFolder() }; const auto localPath{ applicationData.LocalPath() }; @@ -172,6 +202,12 @@ namespace Test::ApplicationData::Tests const auto expectedLocalFolder{ UserLocalFolder(Publisher, Product) }; const auto expectedLocalPath{ UserLocalPath(Publisher, Product) }; VERIFY_ARE_EQUAL(localPath, expectedLocalPath); + } + + TEST_METHOD(SharedLocalFolderAndPath) + { + auto applicationData{ winrt::Microsoft::Windows::Storage::ApplicationData::GetForUnpackaged(Publisher, Product) }; + VERIFY_IS_NOT_NULL(applicationData); try { @@ -192,6 +228,12 @@ namespace Test::ApplicationData::Tests { VERIFY_ARE_EQUAL(E_NOTIMPL, e.code(), WEX::Common::String().Format(L"0x%X %s", e.code(), e.message().c_str())); } + } + + TEST_METHOD(TemporaryFolderAndPath) + { + auto applicationData{ winrt::Microsoft::Windows::Storage::ApplicationData::GetForUnpackaged(Publisher, Product) }; + VERIFY_IS_NOT_NULL(applicationData); const auto temporaryFolder{ applicationData.TemporaryFolder() }; const auto temporaryPath{ applicationData.TemporaryPath() }; From 77800ccf987d9f935a7a9a20486c4bab0d63b314 Mon Sep 17 00:00:00 2001 From: Howard Kapustein Date: Fri, 29 Nov 2024 21:18:45 -0800 Subject: [PATCH 05/24] More tests passing --- .../UnpackagedApplicationData.cpp | 61 +++++++++++++++---- dev/ApplicationData/pch.h | 1 + .../UnpackagedApplicationDataTests.cpp | 24 +++++--- test/ApplicationData/pch.h | 3 + 4 files changed, 69 insertions(+), 20 deletions(-) diff --git a/dev/ApplicationData/UnpackagedApplicationData.cpp b/dev/ApplicationData/UnpackagedApplicationData.cpp index 24fe8879b6..fc7bb96414 100644 --- a/dev/ApplicationData/UnpackagedApplicationData.cpp +++ b/dev/ApplicationData/UnpackagedApplicationData.cpp @@ -34,10 +34,21 @@ namespace Microsoft::Windows::Storage { if (m_localPath.empty()) { - wil::unique_cotaskmem_string localAppData; - //TODO FOLDERID_LocalAppDataLow - THROW_IF_FAILED(SHGetKnownFolderPath(FOLDERID_LocalAppData, KF_FLAG_DEFAULT /*TODO KF_FLAG_CREATE | KF_FLAG_DONT_VERIFY*/, nullptr, wil::out_param(localAppData))); - std::filesystem::path path{ localAppData.get() }; + // Caller is LocalSystem : %PROGRAMDATA%\...publisher...\...product... + // Caller is =MediumIL : %USERPROFILE%\AppData\Local\...publisher...\...product... + auto folderId{ FOLDERID_LocalAppData }; + if (::Security::User::IsLocalSystem()) + { + folderId = FOLDERID_ProgramData; + } + else if (::Security::IntegrityLevel::GetIntegrityLevel() < SECURITY_MANDATORY_MEDIUM_RID) + { + folderId = FOLDERID_LocalAppDataLow; + } + wil::unique_cotaskmem_string rootPath; + THROW_IF_FAILED(SHGetKnownFolderPath(folderId, KF_FLAG_DEFAULT/*TODO KF_FLAG_CREATE | KF_FLAG_DONT_VERIFY*/, nullptr, wil::out_param(rootPath))); + std::filesystem::path path{ rootPath.get() }; path /= m_publisher.c_str(); path /= m_product.c_str(); m_localPath = path.c_str(); @@ -69,23 +80,48 @@ namespace Microsoft::Windows::Storage } winrt::Windows::Storage::StorageFolder UnpackagedApplicationData::LocalCacheFolder() { - return winrt::Windows::Storage::StorageFolder::GetFolderFromPathAsync(LocalCachePath()).get(); + const auto path{ LocalCachePath() }; + if (path.empty()) + { + return nullptr; + } + return winrt::Windows::Storage::StorageFolder::GetFolderFromPathAsync(path).get(); } winrt::Windows::Storage::StorageFolder UnpackagedApplicationData::LocalFolder() { - return winrt::Windows::Storage::StorageFolder::GetFolderFromPathAsync(LocalPath()).get(); + const auto path{ LocalPath() }; + if (path.empty()) + { + return nullptr; + } + return winrt::Windows::Storage::StorageFolder::GetFolderFromPathAsync(path).get(); } winrt::Windows::Storage::StorageFolder UnpackagedApplicationData::MachineFolder() { - return winrt::Windows::Storage::StorageFolder::GetFolderFromPathAsync(MachinePath()).get(); + const auto path{ MachinePath() }; + if (path.empty()) + { + return nullptr; + } + return winrt::Windows::Storage::StorageFolder::GetFolderFromPathAsync(path).get(); } winrt::Windows::Storage::StorageFolder UnpackagedApplicationData::SharedLocalFolder() { - return winrt::Windows::Storage::StorageFolder::GetFolderFromPathAsync(SharedLocalPath()).get(); + const auto path{ SharedLocalPath() }; + if (path.empty()) + { + return nullptr; + } + return winrt::Windows::Storage::StorageFolder::GetFolderFromPathAsync(path).get(); } winrt::Windows::Storage::StorageFolder UnpackagedApplicationData::TemporaryFolder() { - return winrt::Windows::Storage::StorageFolder::GetFolderFromPathAsync(TemporaryPath()).get(); + const auto path{ TemporaryPath() }; + if (path.empty()) + { + return nullptr; + } + return winrt::Windows::Storage::StorageFolder::GetFolderFromPathAsync(path).get(); } winrt::Microsoft::Windows::Storage::ApplicationDataContainer UnpackagedApplicationData::LocalSettings() { @@ -120,10 +156,9 @@ namespace Microsoft::Windows::Storage } std::filesystem::path UnpackagedApplicationData::_MachinePath(winrt::hstring const& publisher, winrt::hstring const& product) { - // Path = %ProgramData%\...publisher...\...product... - wil::unique_cotaskmem_string programData; - THROW_IF_FAILED(SHGetKnownFolderPath(FOLDERID_AppDataProgramData, KF_FLAG_DEFAULT /*TODO KF_FLAG_CREATE | KF_FLAG_DONT_VERIFY*/, nullptr, wil::out_param(programData))); - std::filesystem::path path{ programData.get() }; + wil::unique_cotaskmem_string rootPath; + THROW_IF_FAILED(SHGetKnownFolderPath(FOLDERID_ProgramData, KF_FLAG_DEFAULT/*TODO KF_FLAG_CREATE | KF_FLAG_DONT_VERIFY*/, nullptr, wil::out_param(rootPath))); + std::filesystem::path path{ rootPath.get() }; path /= publisher.c_str(); path /= product.c_str(); diff --git a/dev/ApplicationData/pch.h b/dev/ApplicationData/pch.h index 786111d0d5..d0b9e073a5 100644 --- a/dev/ApplicationData/pch.h +++ b/dev/ApplicationData/pch.h @@ -28,4 +28,5 @@ #include #include +#include #include diff --git a/test/ApplicationData/UnpackagedApplicationDataTests.cpp b/test/ApplicationData/UnpackagedApplicationDataTests.cpp index c0c0879311..152705c6ff 100644 --- a/test/ApplicationData/UnpackagedApplicationDataTests.cpp +++ b/test/ApplicationData/UnpackagedApplicationDataTests.cpp @@ -120,11 +120,21 @@ namespace Test::ApplicationData::Tests std::filesystem::path UserLocalPath(winrt::hstring const& publisher) { - // %LOCALAPPDATA%\...publisher...\...product... - wil::unique_cotaskmem_string localAppData; - //TODO FOLDERID_LocalAppDataLow - VERIFY_SUCCEEDED(SHGetKnownFolderPath(FOLDERID_LocalAppData, KF_FLAG_DEFAULT /*TODO KF_FLAG_CREATE | KF_FLAG_DONT_VERIFY*/, nullptr, wil::out_param(localAppData))); - std::filesystem::path path{ localAppData.get() }; + // Caller is LocalSystem : %PROGRAMDATA%\...publisher...\...product... + // Caller is =MediumIL : %USERPROFILE%\AppData\Local\...publisher...\...product... + auto folderId{ FOLDERID_LocalAppData }; + if (::Security::User::IsLocalSystem()) + { + folderId = FOLDERID_ProgramData; + } + else if (::Security::IntegrityLevel::GetIntegrityLevel() < SECURITY_MANDATORY_MEDIUM_RID) + { + folderId = FOLDERID_LocalAppDataLow; + } + wil::unique_cotaskmem_string rootPath; + VERIFY_SUCCEEDED(SHGetKnownFolderPath(folderId, KF_FLAG_DEFAULT/*TODO KF_FLAG_CREATE | KF_FLAG_DONT_VERIFY*/, nullptr, wil::out_param(rootPath))); + std::filesystem::path path{ rootPath.get() }; path /= publisher.c_str(); return path; } @@ -277,10 +287,10 @@ namespace Test::ApplicationData::Tests VERIFY_IS_FALSE(applicationData.IsMachinePathSupported()); - const auto machineFolder{ applicationData.MachineFolder() }; - VERIFY_IS_NULL(machineFolder); const auto machinePath{ applicationData.MachinePath() }; VERIFY_ARE_EQUAL(machinePath, null_hstring); + const auto machineFolder{ applicationData.MachineFolder() }; + VERIFY_IS_NULL(machineFolder); } TEST_METHOD(LocalSettings) diff --git a/test/ApplicationData/pch.h b/test/ApplicationData/pch.h index 009cc61028..177a9e57a0 100644 --- a/test/ApplicationData/pch.h +++ b/test/ApplicationData/pch.h @@ -11,6 +11,7 @@ #include #include +#include #include #include @@ -25,6 +26,8 @@ #include #include +#include +#include #include #include From da8194909b1751a61c57752774219389878f2317 Mon Sep 17 00:00:00 2001 From: Howard Kapustein Date: Fri, 29 Nov 2024 22:24:50 -0800 Subject: [PATCH 06/24] Various fixes --- .../UnpackagedApplicationData.cpp | 5 ++- dev/ApplicationData/pch.h | 2 ++ dev/Common/Common.vcxitems | 7 +++- dev/Common/Common.vcxitems.filters | 32 +++++++++++++---- dev/Common/Microsoft.FileSystem.Path.h | 34 +++++++++++++++++++ dev/WindowsAppRuntime_DLL/pch.h | 1 + .../UnpackagedApplicationDataTests.cpp | 16 +++++---- test/ApplicationData/pch.h | 1 + 8 files changed, 80 insertions(+), 18 deletions(-) create mode 100644 dev/Common/Microsoft.FileSystem.Path.h diff --git a/dev/ApplicationData/UnpackagedApplicationData.cpp b/dev/ApplicationData/UnpackagedApplicationData.cpp index fc7bb96414..bb0f6909f9 100644 --- a/dev/ApplicationData/UnpackagedApplicationData.cpp +++ b/dev/ApplicationData/UnpackagedApplicationData.cpp @@ -69,9 +69,8 @@ namespace Microsoft::Windows::Storage { if (m_temporaryPath.empty()) { - //TODO LocalSystem - // GetTempPath() + \...publisher...\...product... - auto path{ std::filesystem::temp_directory_path() }; + // GetTempPath[2]() + \...publisher...\...product... + auto path{ ::Microsoft::FileSystem::Path::GetTempDirectory() }; path /= m_publisher.c_str(); path /= m_product.c_str(); m_temporaryPath = path.c_str(); diff --git a/dev/ApplicationData/pch.h b/dev/ApplicationData/pch.h index d0b9e073a5..28bd7e5eb7 100644 --- a/dev/ApplicationData/pch.h +++ b/dev/ApplicationData/pch.h @@ -18,6 +18,7 @@ #include #include #include +#include #include #include @@ -28,5 +29,6 @@ #include #include +#include #include #include diff --git a/dev/Common/Common.vcxitems b/dev/Common/Common.vcxitems index 9fe4e51244..f1ffc2a1df 100644 --- a/dev/Common/Common.vcxitems +++ b/dev/Common/Common.vcxitems @@ -22,8 +22,13 @@ + + + + + @@ -32,4 +37,4 @@ - \ No newline at end of file + diff --git a/dev/Common/Common.vcxitems.filters b/dev/Common/Common.vcxitems.filters index cff6f33620..2d2f4f7f53 100644 --- a/dev/Common/Common.vcxitems.filters +++ b/dev/Common/Common.vcxitems.filters @@ -20,31 +20,49 @@ Header Files - + Header Files - + Header Files - + Header Files - + Header Files Header Files + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + Header Files - + Header Files Header Files - + + Header Files + + Header Files @@ -56,4 +74,4 @@ Source Files - \ No newline at end of file + diff --git a/dev/Common/Microsoft.FileSystem.Path.h b/dev/Common/Microsoft.FileSystem.Path.h new file mode 100644 index 0000000000..08fc1dad0d --- /dev/null +++ b/dev/Common/Microsoft.FileSystem.Path.h @@ -0,0 +1,34 @@ +// Copyright (c) Microsoft Corporation and Contributors. +// Licensed under the MIT License. See LICENSE in the project root for license information. + +#ifndef __MICROSOFT_FILESYSTEM_PATH_H +#define __MICROSOFT_FILESYSTEM_PATH_H + +#include + +#include + +#include + +namespace Microsoft::FileSystem::Path +{ +inline std::filesystem::path GetTempDirectory() +{ + // GetTempPath2() returns ...windir...\SystemTemp if the caller is LocalSystem + // else it's the same as GetTempPath(). However, GetTempPath2() is only available + // on Windows 11 >=10.0.22000.0 (aka 21H2) and we support Windows 10. So handle it... + if (::Security::User::IsLocalSystem()) + { + const auto windowsPath{ wil::GetWindowsDirectoryW() }; + std::filesystem::path path{ windowsPath.get() }; + return path / L"SystemTemp"; + } + else + { + // The Standard C++ Library (on Windows) is equivalent to GetTempPath() + return std::filesystem::temp_directory_path(); + } +} +} + +#endif // __MICROSOFT_FILESYSTEM_PATH_H diff --git a/dev/WindowsAppRuntime_DLL/pch.h b/dev/WindowsAppRuntime_DLL/pch.h index 8dcd8e87ea..1ed0b64bbc 100644 --- a/dev/WindowsAppRuntime_DLL/pch.h +++ b/dev/WindowsAppRuntime_DLL/pch.h @@ -56,6 +56,7 @@ #include #include #include +#include #include #include #include diff --git a/test/ApplicationData/UnpackagedApplicationDataTests.cpp b/test/ApplicationData/UnpackagedApplicationDataTests.cpp index 152705c6ff..b390ef5c58 100644 --- a/test/ApplicationData/UnpackagedApplicationDataTests.cpp +++ b/test/ApplicationData/UnpackagedApplicationDataTests.cpp @@ -120,9 +120,9 @@ namespace Test::ApplicationData::Tests std::filesystem::path UserLocalPath(winrt::hstring const& publisher) { - // Caller is LocalSystem : %PROGRAMDATA%\...publisher...\...product... - // Caller is =MediumIL : %USERPROFILE%\AppData\Local\...publisher...\...product... + // Caller is LocalSystem : %PROGRAMDATA%\...publisher... + // Caller is =MediumIL : %USERPROFILE%\AppData\Local\...publisher... auto folderId{ FOLDERID_LocalAppData }; if (::Security::User::IsLocalSystem()) { @@ -141,6 +141,9 @@ namespace Test::ApplicationData::Tests winrt::hstring UserLocalPath(winrt::hstring const& publisher, winrt::hstring const& product) { + // Caller is LocalSystem : %PROGRAMDATA%\...publisher...\...product... + // Caller is =MediumIL : %USERPROFILE%\AppData\Local\...publisher...\...product... auto path{ UserLocalPath(publisher) }; path /= product.c_str(); return winrt::hstring{ path.c_str() }; @@ -154,16 +157,15 @@ namespace Test::ApplicationData::Tests std::filesystem::path UserTemporaryPath(winrt::hstring const& publisher) { - //TODO LocalSystem - // GetTempPath() + \...publisher...\...product... - auto path{ std::filesystem::temp_directory_path() }; + // GetTempPath[2]() + \...publisher... + auto path{ ::Microsoft::FileSystem::Path::GetTempDirectory() }; path /= publisher.c_str(); return path; } winrt::hstring UserTemporaryPath(winrt::hstring const& publisher, winrt::hstring const& product) { - // %LOCALAPPDATA%\...publisher...\...product... + // GetTempPath[2]() + \...publisher...\...product... auto path{ UserTemporaryPath(publisher) }; path /= product.c_str(); return winrt::hstring{ path.c_str() }; diff --git a/test/ApplicationData/pch.h b/test/ApplicationData/pch.h index 177a9e57a0..81002f7b7c 100644 --- a/test/ApplicationData/pch.h +++ b/test/ApplicationData/pch.h @@ -26,6 +26,7 @@ #include #include +#include #include #include From 2b5fc53f1ecae7f439b5e09fa1882643f50341d8 Mon Sep 17 00:00:00 2001 From: Howard Kapustein Date: Fri, 29 Nov 2024 22:27:36 -0800 Subject: [PATCH 07/24] TODO comments to expedite follow up --- dev/ApplicationData/UnpackagedApplicationData.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/dev/ApplicationData/UnpackagedApplicationData.cpp b/dev/ApplicationData/UnpackagedApplicationData.cpp index bb0f6909f9..9e663d2a3e 100644 --- a/dev/ApplicationData/UnpackagedApplicationData.cpp +++ b/dev/ApplicationData/UnpackagedApplicationData.cpp @@ -124,11 +124,11 @@ namespace Microsoft::Windows::Storage } winrt::Microsoft::Windows::Storage::ApplicationDataContainer UnpackagedApplicationData::LocalSettings() { - throw winrt::hresult_not_implemented(); + throw winrt::hresult_not_implemented(); //TODO LocalSettings } winrt::Windows::Foundation::IAsyncAction UnpackagedApplicationData::ClearAsync(winrt::Microsoft::Windows::Storage::ApplicationDataLocality locality) { - throw winrt::hresult_not_implemented(); + throw winrt::hresult_not_implemented(); //TODO ClearAsync(locality) } winrt::Windows::Foundation::IAsyncAction UnpackagedApplicationData::ClearPublisherCacheFolderAsync(winrt::hstring folderName) { @@ -137,7 +137,7 @@ namespace Microsoft::Windows::Storage } void UnpackagedApplicationData::Close() { - throw winrt::hresult_not_implemented(); + throw winrt::hresult_not_implemented(); //TODO Close } winrt::hstring UnpackagedApplicationData::GetPublisherCachePath(winrt::hstring const& folderName) { @@ -151,7 +151,7 @@ namespace Microsoft::Windows::Storage } winrt::Windows::Foundation::IAsyncAction UnpackagedApplicationData::ClearMachineFolderAsync() { - throw winrt::hresult_not_implemented(); + throw winrt::hresult_not_implemented(); //TODO ClearMachineFolderAsync } std::filesystem::path UnpackagedApplicationData::_MachinePath(winrt::hstring const& publisher, winrt::hstring const& product) { From 30348af1f9592cd6e0a6d4235bd81584741cb4cc Mon Sep 17 00:00:00 2001 From: Howard Kapustein Date: Fri, 6 Mar 2026 01:11:26 -0800 Subject: [PATCH 08/24] Implement UnpackagedApplicationDataContainer --- .../M.W.S.ApplicationDataContainer.cpp | 42 ++ .../M.W.S.ApplicationDataContainer.h | 7 + .../UnpackagedApplicationDataContainer.cpp | 581 ++++++++++++++++++ .../UnpackagedApplicationDataContainer.h | 28 + 4 files changed, 658 insertions(+) create mode 100644 dev/ApplicationData/UnpackagedApplicationDataContainer.cpp create mode 100644 dev/ApplicationData/UnpackagedApplicationDataContainer.h diff --git a/dev/ApplicationData/M.W.S.ApplicationDataContainer.cpp b/dev/ApplicationData/M.W.S.ApplicationDataContainer.cpp index b5181a619f..048f144cc3 100644 --- a/dev/ApplicationData/M.W.S.ApplicationDataContainer.cpp +++ b/dev/ApplicationData/M.W.S.ApplicationDataContainer.cpp @@ -6,14 +6,26 @@ #include "M.W.S.ApplicationDataContainer.h" #include "Microsoft.Windows.Storage.ApplicationDataContainer.g.cpp" +#include "UnpackagedApplicationDataContainer.h" + namespace winrt::Microsoft::Windows::Storage::implementation { ApplicationDataContainer::ApplicationDataContainer(winrt::Windows::Storage::ApplicationDataContainer const& value) : m_applicationDataContainer(value) { } + ApplicationDataContainer::ApplicationDataContainer(std::shared_ptr<::Microsoft::Windows::Storage::UnpackagedApplicationDataContainer> const& value) : + m_unpackagedApplicationDataContainer(value), + m_applicationDataContainer(nullptr) + { + } winrt::Windows::Foundation::Collections::IMap ApplicationDataContainer::Containers() { + if (m_unpackagedApplicationDataContainer) + { + return m_unpackagedApplicationDataContainer->Containers(); + } + winrt::Windows::Foundation::Collections::IMap map{ winrt::single_threaded_map() }; auto containers{ m_applicationDataContainer.Containers() }; for (auto container : containers) @@ -25,10 +37,20 @@ namespace winrt::Microsoft::Windows::Storage::implementation } hstring ApplicationDataContainer::Name() { + if (m_unpackagedApplicationDataContainer) + { + return m_unpackagedApplicationDataContainer->Name(); + } + return m_applicationDataContainer.Name(); } winrt::Microsoft::Windows::Storage::ApplicationDataLocality ApplicationDataContainer::Locality() { + if (m_unpackagedApplicationDataContainer) + { + return m_unpackagedApplicationDataContainer->Locality(); + } + static_assert(static_cast(winrt::Microsoft::Windows::Storage::ApplicationDataLocality::Local) == static_cast(winrt::Windows::Storage::ApplicationDataLocality::Local)); static_assert(static_cast(winrt::Microsoft::Windows::Storage::ApplicationDataLocality::LocalCache) == static_cast(winrt::Windows::Storage::ApplicationDataLocality::LocalCache)); static_assert(static_cast(winrt::Microsoft::Windows::Storage::ApplicationDataLocality::Temporary) == static_cast(winrt::Windows::Storage::ApplicationDataLocality::Temporary)); @@ -37,14 +59,29 @@ namespace winrt::Microsoft::Windows::Storage::implementation } winrt::Windows::Foundation::Collections::IPropertySet ApplicationDataContainer::Values() { + if (m_unpackagedApplicationDataContainer) + { + return m_unpackagedApplicationDataContainer->Values(); + } + return m_applicationDataContainer.Values(); } void ApplicationDataContainer::Close() { + if (m_unpackagedApplicationDataContainer) + { + return m_unpackagedApplicationDataContainer->Close(); + } + return m_applicationDataContainer.Close(); } winrt::Microsoft::Windows::Storage::ApplicationDataContainer ApplicationDataContainer::CreateContainer(hstring const& name, winrt::Microsoft::Windows::Storage::ApplicationDataCreateDisposition const& disposition) { + if (m_unpackagedApplicationDataContainer) + { + return m_unpackagedApplicationDataContainer->CreateContainer(name, disposition); + } + static_assert(static_cast(winrt::Microsoft::Windows::Storage::ApplicationDataCreateDisposition::Always) == static_cast(winrt::Windows::Storage::ApplicationDataCreateDisposition::Always)); static_assert(static_cast(winrt::Microsoft::Windows::Storage::ApplicationDataCreateDisposition::Existing) == static_cast(winrt::Windows::Storage::ApplicationDataCreateDisposition::Existing)); @@ -53,6 +90,11 @@ namespace winrt::Microsoft::Windows::Storage::implementation } void ApplicationDataContainer::DeleteContainer(hstring const& name) { + if (m_unpackagedApplicationDataContainer) + { + return m_unpackagedApplicationDataContainer->DeleteContainer(name); + } + m_applicationDataContainer.DeleteContainer(name); } } diff --git a/dev/ApplicationData/M.W.S.ApplicationDataContainer.h b/dev/ApplicationData/M.W.S.ApplicationDataContainer.h index cb96cb97af..c22bcce957 100644 --- a/dev/ApplicationData/M.W.S.ApplicationDataContainer.h +++ b/dev/ApplicationData/M.W.S.ApplicationDataContainer.h @@ -5,12 +5,18 @@ #include "Microsoft.Windows.Storage.ApplicationDataContainer.g.h" +namespace Microsoft::Windows::Storage +{ + struct UnpackagedApplicationDataContainer; +} + namespace winrt::Microsoft::Windows::Storage::implementation { struct ApplicationDataContainer : ApplicationDataContainerT { ApplicationDataContainer() = default; ApplicationDataContainer(winrt::Windows::Storage::ApplicationDataContainer const& value); + ApplicationDataContainer(std::shared_ptr<::Microsoft::Windows::Storage::UnpackagedApplicationDataContainer> const& value); winrt::Windows::Foundation::Collections::IMap Containers(); hstring Name(); @@ -21,6 +27,7 @@ namespace winrt::Microsoft::Windows::Storage::implementation void DeleteContainer(hstring const& name); private: + std::shared_ptr<::Microsoft::Windows::Storage::UnpackagedApplicationDataContainer> m_unpackagedApplicationDataContainer; winrt::Windows::Storage::ApplicationDataContainer m_applicationDataContainer; }; } diff --git a/dev/ApplicationData/UnpackagedApplicationDataContainer.cpp b/dev/ApplicationData/UnpackagedApplicationDataContainer.cpp new file mode 100644 index 0000000000..fc9f7efaba --- /dev/null +++ b/dev/ApplicationData/UnpackagedApplicationDataContainer.cpp @@ -0,0 +1,581 @@ +// Copyright (c) Microsoft Corporation and Contributors. +// Licensed under the MIT License. + +#include "pch.h" + +#include "UnpackagedApplicationDataContainer.h" + +namespace Microsoft::Windows::Storage +{ + namespace + { + // REG_BINARY values are stored as [1-byte PropertyType tag][raw data bytes]. + // This allows roundtripping types that share the same raw size (e.g. Int32 vs Boolean). + // REG_SZ, REG_DWORD, and REG_QWORD are stored natively for easy manual inspection. + + template + std::vector TaggedBytes(winrt::Windows::Foundation::PropertyType tag, const T& val) + { + std::vector data(1 + sizeof(T)); + data[0] = static_cast(tag); + std::memcpy(data.data() + 1, &val, sizeof(T)); + return data; + } + + winrt::Windows::Foundation::IInspectable ReadRegistryValue(HKEY key, PCWSTR valueName) + { + DWORD type{}; + DWORD dataSize{}; + const auto status{ ::RegQueryValueExW(key, valueName, nullptr, &type, nullptr, &dataSize) }; + THROW_IF_WIN32_ERROR(status); + + switch (type) + { + case REG_SZ: + { + auto value{ wil::reg::get_value_string(key, valueName) }; + return winrt::box_value(winrt::hstring{ value }); + } + case REG_DWORD: + { + auto value{ wil::reg::get_value_dword(key, valueName) }; + return winrt::box_value(value); + } + case REG_QWORD: + { + auto value{ wil::reg::get_value_qword(key, valueName) }; + return winrt::box_value(value); + } + case REG_BINARY: + { + auto bytes{ wil::reg::get_value_binary(key, valueName, REG_BINARY) }; + THROW_HR_IF(E_UNEXPECTED, bytes.empty()); + const auto tag{ static_cast(bytes[0]) }; + const uint8_t* payload{ bytes.data() + 1 }; + const size_t payloadSize{ bytes.size() - 1 }; + + switch (tag) + { + case winrt::Windows::Foundation::PropertyType::UInt8: + { + THROW_HR_IF(E_UNEXPECTED, payloadSize != sizeof(uint8_t)); + uint8_t val{}; + std::memcpy(&val, payload, sizeof(val)); + return winrt::box_value(val); + } + case winrt::Windows::Foundation::PropertyType::Int16: + { + THROW_HR_IF(E_UNEXPECTED, payloadSize != sizeof(int16_t)); + int16_t val{}; + std::memcpy(&val, payload, sizeof(val)); + return winrt::box_value(val); + } + case winrt::Windows::Foundation::PropertyType::UInt16: + { + THROW_HR_IF(E_UNEXPECTED, payloadSize != sizeof(uint16_t)); + uint16_t val{}; + std::memcpy(&val, payload, sizeof(val)); + return winrt::box_value(val); + } + case winrt::Windows::Foundation::PropertyType::Int32: + { + THROW_HR_IF(E_UNEXPECTED, payloadSize != sizeof(int32_t)); + int32_t val{}; + std::memcpy(&val, payload, sizeof(val)); + return winrt::box_value(val); + } + case winrt::Windows::Foundation::PropertyType::Int64: + { + THROW_HR_IF(E_UNEXPECTED, payloadSize != sizeof(int64_t)); + int64_t val{}; + std::memcpy(&val, payload, sizeof(val)); + return winrt::box_value(val); + } + case winrt::Windows::Foundation::PropertyType::Single: + { + THROW_HR_IF(E_UNEXPECTED, payloadSize != sizeof(float)); + float val{}; + std::memcpy(&val, payload, sizeof(val)); + return winrt::box_value(val); + } + case winrt::Windows::Foundation::PropertyType::Double: + { + THROW_HR_IF(E_UNEXPECTED, payloadSize != sizeof(double)); + double val{}; + std::memcpy(&val, payload, sizeof(val)); + return winrt::box_value(val); + } + case winrt::Windows::Foundation::PropertyType::Char16: + { + THROW_HR_IF(E_UNEXPECTED, payloadSize != sizeof(char16_t)); + char16_t val{}; + std::memcpy(&val, payload, sizeof(val)); + return winrt::box_value(val); + } + case winrt::Windows::Foundation::PropertyType::Boolean: + { + THROW_HR_IF(E_UNEXPECTED, payloadSize != sizeof(uint8_t)); + return winrt::box_value(static_cast(payload[0])); + } + case winrt::Windows::Foundation::PropertyType::DateTime: + { + THROW_HR_IF(E_UNEXPECTED, payloadSize != sizeof(int64_t)); + int64_t ticks{}; + std::memcpy(&ticks, payload, sizeof(ticks)); + return winrt::box_value(winrt::Windows::Foundation::DateTime{ winrt::Windows::Foundation::TimeSpan{ ticks } }); + } + case winrt::Windows::Foundation::PropertyType::TimeSpan: + { + THROW_HR_IF(E_UNEXPECTED, payloadSize != sizeof(int64_t)); + int64_t duration{}; + std::memcpy(&duration, payload, sizeof(duration)); + return winrt::box_value(winrt::Windows::Foundation::TimeSpan{ duration }); + } + case winrt::Windows::Foundation::PropertyType::Guid: + { + THROW_HR_IF(E_UNEXPECTED, payloadSize != sizeof(winrt::guid)); + winrt::guid val{}; + std::memcpy(&val, payload, sizeof(val)); + return winrt::box_value(val); + } + case winrt::Windows::Foundation::PropertyType::Point: + { + THROW_HR_IF(E_UNEXPECTED, payloadSize != sizeof(winrt::Windows::Foundation::Point)); + winrt::Windows::Foundation::Point val{}; + std::memcpy(&val, payload, sizeof(val)); + return winrt::box_value(val); + } + case winrt::Windows::Foundation::PropertyType::Size: + { + THROW_HR_IF(E_UNEXPECTED, payloadSize != sizeof(winrt::Windows::Foundation::Size)); + winrt::Windows::Foundation::Size val{}; + std::memcpy(&val, payload, sizeof(val)); + return winrt::box_value(val); + } + case winrt::Windows::Foundation::PropertyType::Rect: + { + THROW_HR_IF(E_UNEXPECTED, payloadSize != sizeof(winrt::Windows::Foundation::Rect)); + winrt::Windows::Foundation::Rect val{}; + std::memcpy(&val, payload, sizeof(val)); + return winrt::box_value(val); + } + default: + THROW_HR(E_UNEXPECTED); + } + } + default: + THROW_HR(E_UNEXPECTED); + } + } + + void WriteRegistryValue(HKEY key, PCWSTR valueName, winrt::Windows::Foundation::IInspectable const& value) + { + auto propertyValue{ value.as() }; + switch (propertyValue.Type()) + { + case winrt::Windows::Foundation::PropertyType::String: + { + auto str{ winrt::unbox_value(value) }; + wil::reg::set_value_string(key, valueName, str.c_str()); + break; + } + case winrt::Windows::Foundation::PropertyType::UInt32: + { + auto val{ winrt::unbox_value(value) }; + wil::reg::set_value_dword(key, valueName, val); + break; + } + case winrt::Windows::Foundation::PropertyType::UInt64: + { + auto val{ winrt::unbox_value(value) }; + wil::reg::set_value_qword(key, valueName, val); + break; + } + case winrt::Windows::Foundation::PropertyType::UInt8: + { + auto val{ winrt::unbox_value(value) }; + wil::reg::set_value_binary(key, valueName, REG_BINARY, TaggedBytes(winrt::Windows::Foundation::PropertyType::UInt8, val)); + break; + } + case winrt::Windows::Foundation::PropertyType::Int16: + { + auto val{ winrt::unbox_value(value) }; + wil::reg::set_value_binary(key, valueName, REG_BINARY, TaggedBytes(winrt::Windows::Foundation::PropertyType::Int16, val)); + break; + } + case winrt::Windows::Foundation::PropertyType::UInt16: + { + auto val{ winrt::unbox_value(value) }; + wil::reg::set_value_binary(key, valueName, REG_BINARY, TaggedBytes(winrt::Windows::Foundation::PropertyType::UInt16, val)); + break; + } + case winrt::Windows::Foundation::PropertyType::Int32: + { + auto val{ winrt::unbox_value(value) }; + wil::reg::set_value_binary(key, valueName, REG_BINARY, TaggedBytes(winrt::Windows::Foundation::PropertyType::Int32, val)); + break; + } + case winrt::Windows::Foundation::PropertyType::Int64: + { + auto val{ winrt::unbox_value(value) }; + wil::reg::set_value_binary(key, valueName, REG_BINARY, TaggedBytes(winrt::Windows::Foundation::PropertyType::Int64, val)); + break; + } + case winrt::Windows::Foundation::PropertyType::Single: + { + auto val{ winrt::unbox_value(value) }; + wil::reg::set_value_binary(key, valueName, REG_BINARY, TaggedBytes(winrt::Windows::Foundation::PropertyType::Single, val)); + break; + } + case winrt::Windows::Foundation::PropertyType::Double: + { + auto val{ winrt::unbox_value(value) }; + wil::reg::set_value_binary(key, valueName, REG_BINARY, TaggedBytes(winrt::Windows::Foundation::PropertyType::Double, val)); + break; + } + case winrt::Windows::Foundation::PropertyType::Char16: + { + auto val{ winrt::unbox_value(value) }; + wil::reg::set_value_binary(key, valueName, REG_BINARY, TaggedBytes(winrt::Windows::Foundation::PropertyType::Char16, val)); + break; + } + case winrt::Windows::Foundation::PropertyType::Boolean: + { + auto val{ winrt::unbox_value(value) }; + uint8_t byte{ val ? static_cast(1) : static_cast(0) }; + wil::reg::set_value_binary(key, valueName, REG_BINARY, TaggedBytes(winrt::Windows::Foundation::PropertyType::Boolean, byte)); + break; + } + case winrt::Windows::Foundation::PropertyType::DateTime: + { + auto val{ propertyValue.GetDateTime() }; + auto ticks{ val.time_since_epoch().count() }; + wil::reg::set_value_binary(key, valueName, REG_BINARY, TaggedBytes(winrt::Windows::Foundation::PropertyType::DateTime, ticks)); + break; + } + case winrt::Windows::Foundation::PropertyType::TimeSpan: + { + auto val{ propertyValue.GetTimeSpan() }; + auto duration{ val.count() }; + wil::reg::set_value_binary(key, valueName, REG_BINARY, TaggedBytes(winrt::Windows::Foundation::PropertyType::TimeSpan, duration)); + break; + } + case winrt::Windows::Foundation::PropertyType::Guid: + { + auto val{ propertyValue.GetGuid() }; + wil::reg::set_value_binary(key, valueName, REG_BINARY, TaggedBytes(winrt::Windows::Foundation::PropertyType::Guid, val)); + break; + } + case winrt::Windows::Foundation::PropertyType::Point: + { + auto val{ propertyValue.GetPoint() }; + wil::reg::set_value_binary(key, valueName, REG_BINARY, TaggedBytes(winrt::Windows::Foundation::PropertyType::Point, val)); + break; + } + case winrt::Windows::Foundation::PropertyType::Size: + { + auto val{ propertyValue.GetSize() }; + wil::reg::set_value_binary(key, valueName, REG_BINARY, TaggedBytes(winrt::Windows::Foundation::PropertyType::Size, val)); + break; + } + case winrt::Windows::Foundation::PropertyType::Rect: + { + auto val{ propertyValue.GetRect() }; + wil::reg::set_value_binary(key, valueName, REG_BINARY, TaggedBytes(winrt::Windows::Foundation::PropertyType::Rect, val)); + break; + } + default: + THROW_HR(E_NOTIMPL); + } + } + + struct RegistryPropertySetIterator : winrt::implements>> + { + RegistryPropertySetIterator(std::vector>&& items) : + m_items(std::move(items)), + m_index(0) + { + } + + winrt::Windows::Foundation::Collections::IKeyValuePair Current() const + { + if (m_index >= m_items.size()) + { + throw winrt::hresult_out_of_bounds(); + } + return m_items[m_index]; + } + + bool HasCurrent() const + { + return m_index < m_items.size(); + } + + bool MoveNext() + { + if (m_index < m_items.size()) + { + ++m_index; + } + return m_index < m_items.size(); + } + + uint32_t GetMany(winrt::array_view> items) + { + uint32_t count{}; + for (auto& item : items) + { + if (m_index >= m_items.size()) + { + break; + } + item = m_items[m_index]; + ++m_index; + ++count; + } + return count; + } + + private: + std::vector> m_items; + uint32_t m_index{}; + }; + + struct RegistryPropertySet : winrt::implements, + winrt::Windows::Foundation::Collections::IMap, + winrt::Windows::Foundation::Collections::IIterable>> + { + RegistryPropertySet(wil::shared_hkey key) : + m_key(std::move(key)) + { + } + + // IMap + winrt::Windows::Foundation::IInspectable Lookup(winrt::hstring const& key) const + { + return ReadRegistryValue(m_key.get(), key.c_str()); + } + + uint32_t Size() const + { + DWORD valueCount{}; + THROW_IF_WIN32_ERROR(::RegQueryInfoKeyW(m_key.get(), nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, &valueCount, nullptr, nullptr, nullptr, nullptr)); + return valueCount; + } + + bool HasKey(winrt::hstring const& key) const + { + DWORD type{}; + const auto status{ ::RegQueryValueExW(m_key.get(), key.c_str(), nullptr, &type, nullptr, nullptr) }; + return status == ERROR_SUCCESS; + } + + winrt::Windows::Foundation::Collections::IMapView GetView() const + { + auto snapshot{ winrt::single_threaded_map() }; + for (const auto& valueData : wil::make_range(wil::reg::value_iterator{ m_key.get() }, wil::reg::value_iterator{})) + { + try + { + auto value{ ReadRegistryValue(m_key.get(), valueData.name.c_str()) }; + snapshot.Insert(winrt::hstring{ valueData.name }, value); + } + catch (...) + { + // Skip values that cannot be read + } + } + return snapshot.GetView(); + } + + bool Insert(winrt::hstring const& key, winrt::Windows::Foundation::IInspectable const& value) + { + bool existed{ HasKey(key) }; + WriteRegistryValue(m_key.get(), key.c_str(), value); + m_mapChanged(*this, winrt::make( + existed ? winrt::Windows::Foundation::Collections::CollectionChange::ItemChanged : winrt::Windows::Foundation::Collections::CollectionChange::ItemInserted, + key)); + return !existed; + } + + void Remove(winrt::hstring const& key) + { + THROW_IF_WIN32_ERROR(::RegDeleteValueW(m_key.get(), key.c_str())); + m_mapChanged(*this, winrt::make( + winrt::Windows::Foundation::Collections::CollectionChange::ItemRemoved, key)); + } + + void Clear() + { + // Collect all value names first, then delete + std::vector names; + for (const auto& valueData : wil::make_range(wil::reg::value_iterator{ m_key.get() }, wil::reg::value_iterator{})) + { + names.push_back(valueData.name); + } + for (const auto& name : names) + { + ::RegDeleteValueW(m_key.get(), name.c_str()); + } + m_mapChanged(*this, winrt::make( + winrt::Windows::Foundation::Collections::CollectionChange::Reset, winrt::hstring{})); + } + + // IIterable + winrt::Windows::Foundation::Collections::IIterator> First() const + { + std::vector> items; + for (const auto& valueData : wil::make_range(wil::reg::value_iterator{ m_key.get() }, wil::reg::value_iterator{})) + { + try + { + auto value{ ReadRegistryValue(m_key.get(), valueData.name.c_str()) }; + items.push_back(winrt::Windows::Foundation::Collections::IKeyValuePair{ + winrt::make(winrt::hstring{ valueData.name }, value) }); + } + catch (...) + { + // Skip values that cannot be read + } + } + return winrt::make(std::move(items)); + } + + // IObservableMap + winrt::event_token MapChanged(winrt::Windows::Foundation::Collections::MapChangedEventHandler const& handler) + { + return m_mapChanged.add(handler); + } + + void MapChanged(winrt::event_token const& token) noexcept + { + m_mapChanged.remove(token); + } + + private: + struct MapChangedEventArgs : winrt::implements> + { + MapChangedEventArgs(winrt::Windows::Foundation::Collections::CollectionChange change, winrt::hstring const& key) : + m_change(change), + m_key(key) + { + } + + winrt::Windows::Foundation::Collections::CollectionChange CollectionChange() const + { + return m_change; + } + + winrt::hstring Key() const + { + return m_key; + } + + private: + winrt::Windows::Foundation::Collections::CollectionChange m_change; + winrt::hstring m_key; + }; + + struct KeyValuePair : winrt::implements> + { + KeyValuePair(winrt::hstring const& key, winrt::Windows::Foundation::IInspectable const& value) : + m_key(key), + m_value(value) + { + } + + winrt::hstring Key() const + { + return m_key; + } + + winrt::Windows::Foundation::IInspectable Value() const + { + return m_value; + } + + private: + winrt::hstring m_key; + winrt::Windows::Foundation::IInspectable m_value; + }; + + wil::shared_hkey m_key; + winrt::event> m_mapChanged; + }; + } + + UnpackagedApplicationDataContainer::UnpackagedApplicationDataContainer( + wil::shared_hkey key, + winrt::hstring const& name, + winrt::Microsoft::Windows::Storage::ApplicationDataLocality locality) : + m_key(std::move(key)), + m_name(name), + m_locality(locality) + { + } + + winrt::Windows::Foundation::Collections::IMap UnpackagedApplicationDataContainer::Containers() + { + auto map{ winrt::single_threaded_map() }; + for (const auto& keyData : wil::make_range(wil::reg::key_iterator{ m_key.get() }, wil::reg::key_iterator{})) + { + auto subKey{ wil::reg::open_shared_key(m_key.get(), keyData.name.c_str(), wil::reg::key_access::readwrite) }; + auto subContainer{ std::make_shared(std::move(subKey), winrt::hstring{ keyData.name }, m_locality) }; + auto wrappedContainer{ winrt::make(subContainer) }; + map.Insert(winrt::hstring{ keyData.name }, wrappedContainer); + } + return map; + } + + winrt::hstring UnpackagedApplicationDataContainer::Name() + { + return m_name; + } + + winrt::Microsoft::Windows::Storage::ApplicationDataLocality UnpackagedApplicationDataContainer::Locality() + { + return m_locality; + } + + winrt::Windows::Foundation::Collections::IPropertySet UnpackagedApplicationDataContainer::Values() + { + return winrt::make(m_key); + } + + void UnpackagedApplicationDataContainer::Close() + { + m_key.reset(); + } + + winrt::Microsoft::Windows::Storage::ApplicationDataContainer UnpackagedApplicationDataContainer::CreateContainer( + winrt::hstring const& name, + winrt::Microsoft::Windows::Storage::ApplicationDataCreateDisposition const& disposition) + { + if (disposition == winrt::Microsoft::Windows::Storage::ApplicationDataCreateDisposition::Existing) + { + auto subKey{ wil::reg::open_shared_key(m_key.get(), name.c_str(), wil::reg::key_access::readwrite) }; + auto subContainer{ std::make_shared(std::move(subKey), name, m_locality) }; + return winrt::make(subContainer); + } + else + { + auto subKey{ wil::reg::create_shared_key(m_key.get(), name.c_str(), wil::reg::key_access::readwrite) }; + auto subContainer{ std::make_shared(std::move(subKey), name, m_locality) }; + return winrt::make(subContainer); + } + } + + void UnpackagedApplicationDataContainer::DeleteContainer(winrt::hstring const& name) + { + const auto hr{ HRESULT_FROM_WIN32(::RegDeleteTreeW(m_key.get(), name.c_str())) }; + if ((hr != HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND)) && (hr != HRESULT_FROM_WIN32(ERROR_PATH_NOT_FOUND))) + { + THROW_IF_WIN32_ERROR_MSG(::RegDeleteTreeW(m_key.get(), name.c_str()), "%ls", name.c_str()); + } + } +} diff --git a/dev/ApplicationData/UnpackagedApplicationDataContainer.h b/dev/ApplicationData/UnpackagedApplicationDataContainer.h new file mode 100644 index 0000000000..3512507a51 --- /dev/null +++ b/dev/ApplicationData/UnpackagedApplicationDataContainer.h @@ -0,0 +1,28 @@ +// Copyright (c) Microsoft Corporation and Contributors. +// Licensed under the MIT License. + +#pragma once + +#include "M.W.S.ApplicationDataContainer.h" + +namespace Microsoft::Windows::Storage +{ + struct UnpackagedApplicationDataContainer + { + UnpackagedApplicationDataContainer() = default; + UnpackagedApplicationDataContainer(wil::shared_hkey key, winrt::hstring const& name, winrt::Microsoft::Windows::Storage::ApplicationDataLocality locality); + + winrt::Windows::Foundation::Collections::IMap Containers(); + winrt::hstring Name(); + winrt::Microsoft::Windows::Storage::ApplicationDataLocality Locality(); + winrt::Windows::Foundation::Collections::IPropertySet Values(); + void Close(); + winrt::Microsoft::Windows::Storage::ApplicationDataContainer CreateContainer(winrt::hstring const& name, winrt::Microsoft::Windows::Storage::ApplicationDataCreateDisposition const& disposition); + void DeleteContainer(winrt::hstring const& name); + + private: + wil::shared_hkey m_key; + winrt::hstring m_name; + winrt::Microsoft::Windows::Storage::ApplicationDataLocality m_locality{}; + }; +} From a85c2d34b99432e4665b856c4ccf9f3093f68064 Mon Sep 17 00:00:00 2001 From: Howard Kapustein Date: Sun, 8 Mar 2026 19:51:59 -0700 Subject: [PATCH 09/24] Added Unpackaged tests. Tweaked Unpackaged implementation --- dev/ApplicationData/ApplicationData.idl | 9 +- .../ApplicationDataTelemetry.h | 12 + .../UnpackagedApplicationData.cpp | 122 +++- .../UnpackagedApplicationData.h | 2 +- .../UnpackagedApplicationDataContainer.cpp | 536 ++++++++++---- .../UnpackagedApplicationDataTests.cpp | 664 ++++++++++++++++++ 6 files changed, 1208 insertions(+), 137 deletions(-) diff --git a/dev/ApplicationData/ApplicationData.idl b/dev/ApplicationData/ApplicationData.idl index ebbbcedd76..1e421111c3 100644 --- a/dev/ApplicationData/ApplicationData.idl +++ b/dev/ApplicationData/ApplicationData.idl @@ -86,7 +86,8 @@ namespace Microsoft.Windows.Storage static ApplicationData GetForPackageFamily(String packageFamilyName); /// Get an instance of ApplicationData for the specified unpackaged app for the current user. - [feature(Feature_ApplicationData)] + /// @note This method respects impersonation. + /// @warning The returned instance of ApplicationData does not support LocalCache, PublisherCache or SharedLocal features. [contract(ApplicationDataContract, 2)] static ApplicationData GetForUnpackaged(String publisher, String product); @@ -95,6 +96,7 @@ namespace Microsoft.Windows.Storage /// Return the path for the local cache data store not included in backup and restore operations. /// @note This is equivalent to Windows.Storage.ApplicationData.LocalCacheFolder().Path() + /// @warning This method is not supported if the ApplicationData instance was acquired via GetForUnpackaged(). /// @see https://learn.microsoft.com/uwp/api/windows.storage.applicationdata.localcachefolder String LocalCachePath { get; }; @@ -111,6 +113,7 @@ namespace Microsoft.Windows.Storage /// Return the path for the shared data store. /// @note This is equivalent to Windows.Storage.ApplicationData.SharedLocalFolder().Path() + /// @warning This method is not supported if the ApplicationData instance was acquired via GetForUnpackaged(). /// @see https://learn.microsoft.com/uwp/api/windows.storage.applicationdata.sharedlocalfolder String SharedLocalPath { get; }; @@ -120,6 +123,7 @@ namespace Microsoft.Windows.Storage String TemporaryPath { get; }; /// Return a StorageFolder for the local cache data store not included in backup and restore operations. + /// @warning This method is not supported if the ApplicationData instance was acquired via GetForUnpackaged(). /// @see https://learn.microsoft.com/uwp/api/windows.storage.applicationdata.localcachefolder Windows.Storage.StorageFolder LocalCacheFolder { get; }; @@ -134,6 +138,7 @@ namespace Microsoft.Windows.Storage Windows.Storage.StorageFolder MachineFolder { get; }; /// Return a StorageFolder for the shared data store. + /// @warning This method is not supported if the ApplicationData instance was acquired via GetForUnpackaged(). /// @see https://learn.microsoft.com/uwp/api/windows.storage.applicationdata.sharedlocalfolder Windows.Storage.StorageFolder SharedLocalFolder { get; }; @@ -157,10 +162,12 @@ namespace Microsoft.Windows.Storage /// Return the specified path of the shared data store for the publisher of the app. /// @note This is equivalent to Windows.Storage.ApplicationData.GetPublisherCacheFolder(folderName).Path() + /// @warning This method is not supported if the ApplicationData instance was acquired via GetForUnpackaged(). /// @see https://learn.microsoft.com/uwp/api/windows.storage.applicationdata.getpublishercachefolder String GetPublisherCachePath(String folderName); /// Return the specified subfolder of the shared data store for the publisher of the app. + /// @warning This method is not supported if the ApplicationData instance was acquired via GetForUnpackaged(). /// @see https://learn.microsoft.com/uwp/api/windows.storage.applicationdata.getpublishercachefolder Windows.Storage.StorageFolder GetPublisherCacheFolder(String folderName); } diff --git a/dev/ApplicationData/ApplicationDataTelemetry.h b/dev/ApplicationData/ApplicationDataTelemetry.h index 9ce1d58fc5..4dc8478805 100644 --- a/dev/ApplicationData/ApplicationDataTelemetry.h +++ b/dev/ApplicationData/ApplicationDataTelemetry.h @@ -26,4 +26,16 @@ class ApplicationDataTelemetry : public wil::TraceLoggingProvider } CATCH_LOG() END_ACTIVITY_CLASS(); + BEGIN_COMPLIANT_MEASURES_ACTIVITY_CLASS(UnpackagedClearAsync, PDT_ProductAndServicePerformance); + DEFINE_ACTIVITY_START(winrt::Microsoft::Windows::Storage::ApplicationDataLocality locality, winrt::hstring const& publisher, winrt::hstring const& product) noexcept try + { + TraceLoggingClassWriteStart( + UnpackagedClearAsync, + _GENERIC_PARTB_FIELDS_ENABLED, + TraceLoggingInt32(static_cast(locality), "Locality", + TraceLoggingWideString(publisher.c_str(), "Publisher"), + TraceLoggingWideString(product.c_str(), "Product")); + } + CATCH_LOG() + END_ACTIVITY_CLASS(); }; diff --git a/dev/ApplicationData/UnpackagedApplicationData.cpp b/dev/ApplicationData/UnpackagedApplicationData.cpp index 9e663d2a3e..6663000edf 100644 --- a/dev/ApplicationData/UnpackagedApplicationData.cpp +++ b/dev/ApplicationData/UnpackagedApplicationData.cpp @@ -8,6 +8,7 @@ #include #include "UnpackagedApplicationData.h" +#include "UnpackagedApplicationDataContainer.h" #include "ApplicationDataTelemetry.h" @@ -22,16 +23,22 @@ namespace Microsoft::Windows::Storage } bool UnpackagedApplicationData::IsMachinePathSupported() { + _VerifyNotClosed(); + const auto path{ _MachinePath(m_publisher, m_product) }; return !path.empty(); } winrt::hstring UnpackagedApplicationData::LocalCachePath() { + _VerifyNotClosed(); + // ApplicationData.LocalCacheFolder has no unpackaged equivalent throw winrt::hresult_not_implemented(); } winrt::hstring UnpackagedApplicationData::LocalPath() { + _VerifyNotClosed(); + if (m_localPath.empty()) { // Caller is LocalSystem : %PROGRAMDATA%\...publisher...\...product... @@ -57,16 +64,22 @@ namespace Microsoft::Windows::Storage } winrt::hstring UnpackagedApplicationData::MachinePath() { + _VerifyNotClosed(); + const auto path{ _MachinePath(m_publisher, m_product) }; return winrt::hstring{ path.c_str() }; } winrt::hstring UnpackagedApplicationData::SharedLocalPath() { + _VerifyNotClosed(); + // ApplicationData.SharedLocalFolder has no unpackaged equivalent throw winrt::hresult_not_implemented(); } winrt::hstring UnpackagedApplicationData::TemporaryPath() { + _VerifyNotClosed(); + if (m_temporaryPath.empty()) { // GetTempPath[2]() + \...publisher...\...product... @@ -79,6 +92,8 @@ namespace Microsoft::Windows::Storage } winrt::Windows::Storage::StorageFolder UnpackagedApplicationData::LocalCacheFolder() { + _VerifyNotClosed(); + const auto path{ LocalCachePath() }; if (path.empty()) { @@ -88,6 +103,8 @@ namespace Microsoft::Windows::Storage } winrt::Windows::Storage::StorageFolder UnpackagedApplicationData::LocalFolder() { + _VerifyNotClosed(); + const auto path{ LocalPath() }; if (path.empty()) { @@ -97,6 +114,8 @@ namespace Microsoft::Windows::Storage } winrt::Windows::Storage::StorageFolder UnpackagedApplicationData::MachineFolder() { + _VerifyNotClosed(); + const auto path{ MachinePath() }; if (path.empty()) { @@ -106,6 +125,8 @@ namespace Microsoft::Windows::Storage } winrt::Windows::Storage::StorageFolder UnpackagedApplicationData::SharedLocalFolder() { + _VerifyNotClosed(); + const auto path{ SharedLocalPath() }; if (path.empty()) { @@ -115,6 +136,8 @@ namespace Microsoft::Windows::Storage } winrt::Windows::Storage::StorageFolder UnpackagedApplicationData::TemporaryFolder() { + _VerifyNotClosed(); + const auto path{ TemporaryPath() }; if (path.empty()) { @@ -124,34 +147,123 @@ namespace Microsoft::Windows::Storage } winrt::Microsoft::Windows::Storage::ApplicationDataContainer UnpackagedApplicationData::LocalSettings() { - throw winrt::hresult_not_implemented(); //TODO LocalSettings + _VerifyNotClosed(); + + wil::unique_hkey currentUserKey; + THROW_IF_WIN32_ERROR(::RegOpenCurrentUser(KEY_READ | KEY_WRITE, currentUserKey.put())); + auto subKey{ std::format(L"SOFTWARE\\{}\\{}", m_publisher, m_product) }; + auto key{ wil::reg::create_shared_key(currentUserKey.get(), subKey.c_str(), wil::reg::key_access::readwrite) }; + auto container{ std::make_shared( + std::move(key), winrt::hstring{}, winrt::Microsoft::Windows::Storage::ApplicationDataLocality::Local) }; + return winrt::make(container); } winrt::Windows::Foundation::IAsyncAction UnpackagedApplicationData::ClearAsync(winrt::Microsoft::Windows::Storage::ApplicationDataLocality locality) { - throw winrt::hresult_not_implemented(); //TODO ClearAsync(locality) + _VerifyNotClosed(); + + auto logTelemetry{ ApplicationDataTelemetry::UnpackagedClearAsync::Start(locality, m_publisher, m_product) }; + + auto strong{ get_strong() }; + + logTelemetry.IgnoreCurrentThread(); + co_await winrt::resume_background(); + auto logTelemetryContinuation{ logTelemetry.ContinueOnCurrentThread() }; + + switch (locality) + { + case winrt::Microsoft::Windows::Storage::ApplicationDataLocality::Local: + { + // Clear local path + const auto localPath{ LocalPath() }; + if (!localPath.empty()) + { + std::filesystem::path path{ localPath.c_str() }; + if (_PathExists(path)) + { + const auto options{ wil::RemoveDirectoryOptions::KeepRootDirectory | wil::RemoveDirectoryOptions::RemoveReadOnly }; + THROW_IF_FAILED(wil::RemoveDirectoryRecursiveNoThrow(path.c_str(), options)); + } + } + + // Clear local settings + wil::unique_hkey currentUserKey; + THROW_IF_WIN32_ERROR(::RegOpenCurrentUser(KEY_READ | KEY_WRITE, currentUserKey.put())); + auto subKey{ std::format(L"SOFTWARE\\{}\\{}", m_publisher, m_product) }; + const auto hr{ HRESULT_FROM_WIN32(::RegDeleteTreeW(currentUserKey.get(), subKey.c_str())) }; + if (hr != HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND) && hr != HRESULT_FROM_WIN32(ERROR_PATH_NOT_FOUND)) + { + THROW_IF_FAILED(hr); + } + break; + } + case winrt::Microsoft::Windows::Storage::ApplicationDataLocality::Temporary: + { + const auto temporaryPath{ TemporaryPath() }; + if (!temporaryPath.empty()) + { + std::filesystem::path path{ temporaryPath.c_str() }; + if (_PathExists(path)) + { + const auto options{ wil::RemoveDirectoryOptions::KeepRootDirectory | wil::RemoveDirectoryOptions::RemoveReadOnly }; + THROW_IF_FAILED(wil::RemoveDirectoryRecursiveNoThrow(path.c_str(), options)); + } + } + break; + } + case winrt::Microsoft::Windows::Storage::ApplicationDataLocality::Machine: + { + const auto machinePath{ MachinePath() }; + if (!machinePath.empty()) + { + std::filesystem::path path{ machinePath.c_str() }; + if (_PathExists(path)) + { + const auto options{ wil::RemoveDirectoryOptions::KeepRootDirectory | wil::RemoveDirectoryOptions::RemoveReadOnly }; + THROW_IF_FAILED(wil::RemoveDirectoryRecursiveNoThrow(path.c_str(), options)); + } + } + break; + } + default: + THROW_HR_MSG(E_INVALIDARG, "%d", static_cast(locality)); + } + + logTelemetry.Stop(); + + co_return; } winrt::Windows::Foundation::IAsyncAction UnpackagedApplicationData::ClearPublisherCacheFolderAsync(winrt::hstring folderName) { + _VerifyNotClosed(); + // ApplicationData.PublisherCacheFolder has no unpackaged equivalent throw winrt::hresult_not_implemented(); } void UnpackagedApplicationData::Close() { - throw winrt::hresult_not_implemented(); //TODO Close + m_publisher.clear(); + m_product.clear(); + m_localPath.clear(); + m_machinePath.clear(); + m_temporaryPath.clear(); } winrt::hstring UnpackagedApplicationData::GetPublisherCachePath(winrt::hstring const& folderName) { + _VerifyNotClosed(); + // ApplicationData.PublisherCacheFolder has no unpackaged equivalent throw winrt::hresult_not_implemented(); } winrt::Windows::Storage::StorageFolder UnpackagedApplicationData::GetPublisherCacheFolder(winrt::hstring const& folderName) { + _VerifyNotClosed(); + // ApplicationData.PublisherCacheFolder has no unpackaged equivalent throw winrt::hresult_not_implemented(); } - winrt::Windows::Foundation::IAsyncAction UnpackagedApplicationData::ClearMachineFolderAsync() + void UnpackagedApplicationData::_VerifyNotClosed() { - throw winrt::hresult_not_implemented(); //TODO ClearMachineFolderAsync + THROW_HR_IF(RO_E_CLOSED, m_publisher.empty()); } std::filesystem::path UnpackagedApplicationData::_MachinePath(winrt::hstring const& publisher, winrt::hstring const& product) { diff --git a/dev/ApplicationData/UnpackagedApplicationData.h b/dev/ApplicationData/UnpackagedApplicationData.h index 677448de98..1b23c70e8e 100644 --- a/dev/ApplicationData/UnpackagedApplicationData.h +++ b/dev/ApplicationData/UnpackagedApplicationData.h @@ -31,7 +31,7 @@ namespace Microsoft::Windows::Storage winrt::Windows::Storage::StorageFolder GetPublisherCacheFolder(winrt::hstring const& folderName); private: - winrt::Windows::Foundation::IAsyncAction ClearMachineFolderAsync(); + void _VerifyNotClosed(); static std::filesystem::path _MachinePath(winrt::hstring const& publisher, winrt::hstring const& product); static bool _PathExists(std::filesystem::path const& path); diff --git a/dev/ApplicationData/UnpackagedApplicationDataContainer.cpp b/dev/ApplicationData/UnpackagedApplicationDataContainer.cpp index fc9f7efaba..42b3c6874c 100644 --- a/dev/ApplicationData/UnpackagedApplicationDataContainer.cpp +++ b/dev/ApplicationData/UnpackagedApplicationDataContainer.cpp @@ -9,17 +9,91 @@ namespace Microsoft::Windows::Storage { namespace { - // REG_BINARY values are stored as [1-byte PropertyType tag][raw data bytes]. - // This allows roundtripping types that share the same raw size (e.g. Int32 vs Boolean). - // REG_SZ, REG_DWORD, and REG_QWORD are stored natively for easy manual inspection. + // Custom registry types for WinRT PropertyType values that don't have native + // registry equivalents. Modeled after Windows' STATE_REG_TYPE used in + // onecore\base\appmodel\statemanager. Standard registry types (REG_SZ, + // REG_DWORD, REG_QWORD) are used for String, UInt32, and UInt64. Custom + // types start at PropertyType value + 0x20000 to avoid collisions with standard registry types. + enum ApplicationDataRegistryType : DWORD + { + // Scalar types: PropertyType enum value + 0x20000 + ApplicationDataRegistryType_UInt8 = 0x20001, // PropertyType::UInt8 (1) + ApplicationDataRegistryType_Int16 = 0x20002, // PropertyType::Int16 (2) + ApplicationDataRegistryType_UInt16 = 0x20003, // PropertyType::UInt16 (3) + ApplicationDataRegistryType_Int32 = 0x20004, // PropertyType::Int32 (4) + // UInt32 (5) -> REG_DWORD + ApplicationDataRegistryType_Int64 = 0x20006, // PropertyType::Int64 (6) + // UInt64 (7) -> REG_QWORD + ApplicationDataRegistryType_Single = 0x20008, // PropertyType::Single (8) + ApplicationDataRegistryType_Double = 0x20009, // PropertyType::Double (9) + ApplicationDataRegistryType_Char16 = 0x2000A, // PropertyType::Char16 (10) + ApplicationDataRegistryType_Boolean = 0x2000B, // PropertyType::Boolean (11) + // String (12) -> REG_SZ + ApplicationDataRegistryType_DateTime = 0x2000E, // PropertyType::DateTime (14) + ApplicationDataRegistryType_TimeSpan = 0x2000F, // PropertyType::TimeSpan (15) + ApplicationDataRegistryType_Guid = 0x20010, // PropertyType::Guid (16) + ApplicationDataRegistryType_Point = 0x20011, // PropertyType::Point (17) + ApplicationDataRegistryType_Size = 0x20012, // PropertyType::Size (18) + ApplicationDataRegistryType_Rect = 0x20013, // PropertyType::Rect (19) + // Array types: PropertyType enum value + 0x20000 + ApplicationDataRegistryType_UInt8Array = 0x20401, // PropertyType::UInt8Array (1025) + ApplicationDataRegistryType_Int16Array = 0x20402, // PropertyType::Int16Array (1026) + ApplicationDataRegistryType_UInt16Array = 0x20403, // PropertyType::UInt16Array (1027) + ApplicationDataRegistryType_Int32Array = 0x20404, // PropertyType::Int32Array (1028) + ApplicationDataRegistryType_UInt32Array = 0x20405, // PropertyType::UInt32Array (1029) + ApplicationDataRegistryType_Int64Array = 0x20406, // PropertyType::Int64Array (1030) + ApplicationDataRegistryType_UInt64Array = 0x20407, // PropertyType::UInt64Array (1031) + ApplicationDataRegistryType_SingleArray = 0x20408, // PropertyType::SingleArray (1032) + ApplicationDataRegistryType_DoubleArray = 0x20409, // PropertyType::DoubleArray (1033) + ApplicationDataRegistryType_Char16Array = 0x2040A, // PropertyType::Char16Array (1034) + ApplicationDataRegistryType_BooleanArray = 0x2040B, // PropertyType::BooleanArray (1035) + // StringArray (1036) -> REG_MULTI_SZ + ApplicationDataRegistryType_DateTimeArray = 0x2040E, // PropertyType::DateTimeArray (1038) + ApplicationDataRegistryType_TimeSpanArray = 0x2040F, // PropertyType::TimeSpanArray (1039) + ApplicationDataRegistryType_GuidArray = 0x20410, // PropertyType::GuidArray (1040) + ApplicationDataRegistryType_PointArray = 0x20411, // PropertyType::PointArray (1041) + ApplicationDataRegistryType_SizeArray = 0x20412, // PropertyType::SizeArray (1042) + ApplicationDataRegistryType_RectArray = 0x20413, // PropertyType::RectArray (1043) + }; + + template + void SetCustomTypeValue(HKEY key, PCWSTR valueName, DWORD regType, const T& val) + { + THROW_IF_WIN32_ERROR(::RegSetValueExW( + key, valueName, 0, regType, + reinterpret_cast(&val), sizeof(T))); + } + + template + T ReadCustomTypeValue(HKEY key, PCWSTR valueName, DWORD dataSize) + { + THROW_HR_IF(E_UNEXPECTED, dataSize != sizeof(T)); + T val{}; + THROW_IF_WIN32_ERROR(::RegQueryValueExW( + key, valueName, nullptr, nullptr, + reinterpret_cast(&val), &dataSize)); + return val; + } + + template + void SetCustomTypeArrayValue(HKEY key, PCWSTR valueName, DWORD regType, const T* data, uint32_t count) + { + THROW_IF_WIN32_ERROR(::RegSetValueExW( + key, valueName, 0, regType, + reinterpret_cast(data), + static_cast(count * sizeof(T)))); + } template - std::vector TaggedBytes(winrt::Windows::Foundation::PropertyType tag, const T& val) + winrt::com_array ReadCustomTypeArrayValue(HKEY key, PCWSTR valueName, DWORD dataSize) { - std::vector data(1 + sizeof(T)); - data[0] = static_cast(tag); - std::memcpy(data.data() + 1, &val, sizeof(T)); - return data; + THROW_HR_IF(E_UNEXPECTED, dataSize % sizeof(T) != 0); + const uint32_t count{ dataSize / static_cast(sizeof(T)) }; + winrt::com_array arr(count); + THROW_IF_WIN32_ERROR(::RegQueryValueExW( + key, valueName, nullptr, nullptr, + reinterpret_cast(arr.data()), &dataSize)); + return arr; } winrt::Windows::Foundation::IInspectable ReadRegistryValue(HKEY key, PCWSTR valueName) @@ -31,6 +105,8 @@ namespace Microsoft::Windows::Storage switch (type) { + case REG_NONE: + return nullptr; case REG_SZ: { auto value{ wil::reg::get_value_string(key, valueName) }; @@ -46,122 +122,157 @@ namespace Microsoft::Windows::Storage auto value{ wil::reg::get_value_qword(key, valueName) }; return winrt::box_value(value); } - case REG_BINARY: + case REG_MULTI_SZ: { - auto bytes{ wil::reg::get_value_binary(key, valueName, REG_BINARY) }; - THROW_HR_IF(E_UNEXPECTED, bytes.empty()); - const auto tag{ static_cast(bytes[0]) }; - const uint8_t* payload{ bytes.data() + 1 }; - const size_t payloadSize{ bytes.size() - 1 }; - - switch (tag) - { - case winrt::Windows::Foundation::PropertyType::UInt8: - { - THROW_HR_IF(E_UNEXPECTED, payloadSize != sizeof(uint8_t)); - uint8_t val{}; - std::memcpy(&val, payload, sizeof(val)); - return winrt::box_value(val); - } - case winrt::Windows::Foundation::PropertyType::Int16: - { - THROW_HR_IF(E_UNEXPECTED, payloadSize != sizeof(int16_t)); - int16_t val{}; - std::memcpy(&val, payload, sizeof(val)); - return winrt::box_value(val); - } - case winrt::Windows::Foundation::PropertyType::UInt16: - { - THROW_HR_IF(E_UNEXPECTED, payloadSize != sizeof(uint16_t)); - uint16_t val{}; - std::memcpy(&val, payload, sizeof(val)); - return winrt::box_value(val); - } - case winrt::Windows::Foundation::PropertyType::Int32: - { - THROW_HR_IF(E_UNEXPECTED, payloadSize != sizeof(int32_t)); - int32_t val{}; - std::memcpy(&val, payload, sizeof(val)); - return winrt::box_value(val); - } - case winrt::Windows::Foundation::PropertyType::Int64: - { - THROW_HR_IF(E_UNEXPECTED, payloadSize != sizeof(int64_t)); - int64_t val{}; - std::memcpy(&val, payload, sizeof(val)); - return winrt::box_value(val); - } - case winrt::Windows::Foundation::PropertyType::Single: - { - THROW_HR_IF(E_UNEXPECTED, payloadSize != sizeof(float)); - float val{}; - std::memcpy(&val, payload, sizeof(val)); - return winrt::box_value(val); - } - case winrt::Windows::Foundation::PropertyType::Double: - { - THROW_HR_IF(E_UNEXPECTED, payloadSize != sizeof(double)); - double val{}; - std::memcpy(&val, payload, sizeof(val)); - return winrt::box_value(val); - } - case winrt::Windows::Foundation::PropertyType::Char16: - { - THROW_HR_IF(E_UNEXPECTED, payloadSize != sizeof(char16_t)); - char16_t val{}; - std::memcpy(&val, payload, sizeof(val)); - return winrt::box_value(val); - } - case winrt::Windows::Foundation::PropertyType::Boolean: - { - THROW_HR_IF(E_UNEXPECTED, payloadSize != sizeof(uint8_t)); - return winrt::box_value(static_cast(payload[0])); - } - case winrt::Windows::Foundation::PropertyType::DateTime: - { - THROW_HR_IF(E_UNEXPECTED, payloadSize != sizeof(int64_t)); - int64_t ticks{}; - std::memcpy(&ticks, payload, sizeof(ticks)); - return winrt::box_value(winrt::Windows::Foundation::DateTime{ winrt::Windows::Foundation::TimeSpan{ ticks } }); - } - case winrt::Windows::Foundation::PropertyType::TimeSpan: + std::vector buffer(dataSize); + THROW_IF_WIN32_ERROR(::RegQueryValueExW(key, valueName, nullptr, nullptr, buffer.data(), &dataSize)); + auto* p{ reinterpret_cast(buffer.data()) }; + auto* end{ reinterpret_cast(buffer.data() + dataSize) }; + std::vector strings; + while (p < end && *p != L'\0') { - THROW_HR_IF(E_UNEXPECTED, payloadSize != sizeof(int64_t)); - int64_t duration{}; - std::memcpy(&duration, payload, sizeof(duration)); - return winrt::box_value(winrt::Windows::Foundation::TimeSpan{ duration }); + winrt::hstring str{ p }; + strings.push_back(str); + p += str.size() + 1; } - case winrt::Windows::Foundation::PropertyType::Guid: - { - THROW_HR_IF(E_UNEXPECTED, payloadSize != sizeof(winrt::guid)); - winrt::guid val{}; - std::memcpy(&val, payload, sizeof(val)); - return winrt::box_value(val); - } - case winrt::Windows::Foundation::PropertyType::Point: + winrt::com_array arr(strings.begin(), strings.end()); + return winrt::Windows::Foundation::PropertyValue::CreateStringArray(arr); + } + case ApplicationDataRegistryType_UInt8: + return winrt::box_value(ReadCustomTypeValue(key, valueName, dataSize)); + case ApplicationDataRegistryType_Int16: + return winrt::box_value(ReadCustomTypeValue(key, valueName, dataSize)); + case ApplicationDataRegistryType_UInt16: + return winrt::box_value(ReadCustomTypeValue(key, valueName, dataSize)); + case ApplicationDataRegistryType_Int32: + return winrt::box_value(ReadCustomTypeValue(key, valueName, dataSize)); + case ApplicationDataRegistryType_Int64: + return winrt::box_value(ReadCustomTypeValue(key, valueName, dataSize)); + case ApplicationDataRegistryType_Single: + return winrt::box_value(ReadCustomTypeValue(key, valueName, dataSize)); + case ApplicationDataRegistryType_Double: + return winrt::box_value(ReadCustomTypeValue(key, valueName, dataSize)); + case ApplicationDataRegistryType_Char16: + return winrt::box_value(ReadCustomTypeValue(key, valueName, dataSize)); + case ApplicationDataRegistryType_Boolean: + return winrt::box_value(static_cast(ReadCustomTypeValue(key, valueName, dataSize))); + case ApplicationDataRegistryType_DateTime: + { + auto ticks{ ReadCustomTypeValue(key, valueName, dataSize) }; + return winrt::box_value(winrt::Windows::Foundation::DateTime{ winrt::Windows::Foundation::TimeSpan{ ticks } }); + } + case ApplicationDataRegistryType_TimeSpan: + { + auto duration{ ReadCustomTypeValue(key, valueName, dataSize) }; + return winrt::box_value(winrt::Windows::Foundation::TimeSpan{ duration }); + } + case ApplicationDataRegistryType_Guid: + return winrt::box_value(ReadCustomTypeValue(key, valueName, dataSize)); + case ApplicationDataRegistryType_Point: + return winrt::box_value(ReadCustomTypeValue(key, valueName, dataSize)); + case ApplicationDataRegistryType_Size: + return winrt::box_value(ReadCustomTypeValue(key, valueName, dataSize)); + case ApplicationDataRegistryType_Rect: + return winrt::box_value(ReadCustomTypeValue(key, valueName, dataSize)); + case ApplicationDataRegistryType_UInt8Array: + { + auto arr{ ReadCustomTypeArrayValue(key, valueName, dataSize) }; + return winrt::Windows::Foundation::PropertyValue::CreateUInt8Array(arr); + } + case ApplicationDataRegistryType_Int16Array: + { + auto arr{ ReadCustomTypeArrayValue(key, valueName, dataSize) }; + return winrt::Windows::Foundation::PropertyValue::CreateInt16Array(arr); + } + case ApplicationDataRegistryType_UInt16Array: + { + auto arr{ ReadCustomTypeArrayValue(key, valueName, dataSize) }; + return winrt::Windows::Foundation::PropertyValue::CreateUInt16Array(arr); + } + case ApplicationDataRegistryType_Int32Array: + { + auto arr{ ReadCustomTypeArrayValue(key, valueName, dataSize) }; + return winrt::Windows::Foundation::PropertyValue::CreateInt32Array(arr); + } + case ApplicationDataRegistryType_UInt32Array: + { + auto arr{ ReadCustomTypeArrayValue(key, valueName, dataSize) }; + return winrt::Windows::Foundation::PropertyValue::CreateUInt32Array(arr); + } + case ApplicationDataRegistryType_Int64Array: + { + auto arr{ ReadCustomTypeArrayValue(key, valueName, dataSize) }; + return winrt::Windows::Foundation::PropertyValue::CreateInt64Array(arr); + } + case ApplicationDataRegistryType_UInt64Array: + { + auto arr{ ReadCustomTypeArrayValue(key, valueName, dataSize) }; + return winrt::Windows::Foundation::PropertyValue::CreateUInt64Array(arr); + } + case ApplicationDataRegistryType_SingleArray: + { + auto arr{ ReadCustomTypeArrayValue(key, valueName, dataSize) }; + return winrt::Windows::Foundation::PropertyValue::CreateSingleArray(arr); + } + case ApplicationDataRegistryType_DoubleArray: + { + auto arr{ ReadCustomTypeArrayValue(key, valueName, dataSize) }; + return winrt::Windows::Foundation::PropertyValue::CreateDoubleArray(arr); + } + case ApplicationDataRegistryType_Char16Array: + { + auto arr{ ReadCustomTypeArrayValue(key, valueName, dataSize) }; + return winrt::Windows::Foundation::PropertyValue::CreateChar16Array(arr); + } + case ApplicationDataRegistryType_BooleanArray: + { + auto bytes{ ReadCustomTypeArrayValue(key, valueName, dataSize) }; + winrt::com_array arr(bytes.size()); + for (uint32_t i = 0; i < bytes.size(); ++i) { - THROW_HR_IF(E_UNEXPECTED, payloadSize != sizeof(winrt::Windows::Foundation::Point)); - winrt::Windows::Foundation::Point val{}; - std::memcpy(&val, payload, sizeof(val)); - return winrt::box_value(val); + arr[i] = static_cast(bytes[i]); } - case winrt::Windows::Foundation::PropertyType::Size: + return winrt::Windows::Foundation::PropertyValue::CreateBooleanArray(arr); + } + case ApplicationDataRegistryType_DateTimeArray: + { + auto ticks{ ReadCustomTypeArrayValue(key, valueName, dataSize) }; + winrt::com_array arr(ticks.size()); + for (uint32_t i = 0; i < ticks.size(); ++i) { - THROW_HR_IF(E_UNEXPECTED, payloadSize != sizeof(winrt::Windows::Foundation::Size)); - winrt::Windows::Foundation::Size val{}; - std::memcpy(&val, payload, sizeof(val)); - return winrt::box_value(val); + arr[i] = winrt::Windows::Foundation::DateTime{ winrt::Windows::Foundation::TimeSpan{ ticks[i] } }; } - case winrt::Windows::Foundation::PropertyType::Rect: + return winrt::Windows::Foundation::PropertyValue::CreateDateTimeArray(arr); + } + case ApplicationDataRegistryType_TimeSpanArray: + { + auto durations{ ReadCustomTypeArrayValue(key, valueName, dataSize) }; + winrt::com_array arr(durations.size()); + for (uint32_t i = 0; i < durations.size(); ++i) { - THROW_HR_IF(E_UNEXPECTED, payloadSize != sizeof(winrt::Windows::Foundation::Rect)); - winrt::Windows::Foundation::Rect val{}; - std::memcpy(&val, payload, sizeof(val)); - return winrt::box_value(val); - } - default: - THROW_HR(E_UNEXPECTED); + arr[i] = winrt::Windows::Foundation::TimeSpan{ durations[i] }; } + return winrt::Windows::Foundation::PropertyValue::CreateTimeSpanArray(arr); + } + case ApplicationDataRegistryType_GuidArray: + { + auto arr{ ReadCustomTypeArrayValue(key, valueName, dataSize) }; + return winrt::Windows::Foundation::PropertyValue::CreateGuidArray(arr); + } + case ApplicationDataRegistryType_PointArray: + { + auto arr{ ReadCustomTypeArrayValue(key, valueName, dataSize) }; + return winrt::Windows::Foundation::PropertyValue::CreatePointArray(arr); + } + case ApplicationDataRegistryType_SizeArray: + { + auto arr{ ReadCustomTypeArrayValue(key, valueName, dataSize) }; + return winrt::Windows::Foundation::PropertyValue::CreateSizeArray(arr); + } + case ApplicationDataRegistryType_RectArray: + { + auto arr{ ReadCustomTypeArrayValue(key, valueName, dataSize) }; + return winrt::Windows::Foundation::PropertyValue::CreateRectArray(arr); } default: THROW_HR(E_UNEXPECTED); @@ -170,9 +281,21 @@ namespace Microsoft::Windows::Storage void WriteRegistryValue(HKEY key, PCWSTR valueName, winrt::Windows::Foundation::IInspectable const& value) { + if (!value) + { + THROW_IF_WIN32_ERROR(::RegSetValueExW( + key, valueName, 0, REG_NONE, nullptr, 0)); + return; + } auto propertyValue{ value.as() }; switch (propertyValue.Type()) { + case winrt::Windows::Foundation::PropertyType::Empty: + { + THROW_IF_WIN32_ERROR(::RegSetValueExW( + key, valueName, 0, REG_NONE, nullptr, 0)); + break; + } case winrt::Windows::Foundation::PropertyType::String: { auto str{ winrt::unbox_value(value) }; @@ -194,94 +317,247 @@ namespace Microsoft::Windows::Storage case winrt::Windows::Foundation::PropertyType::UInt8: { auto val{ winrt::unbox_value(value) }; - wil::reg::set_value_binary(key, valueName, REG_BINARY, TaggedBytes(winrt::Windows::Foundation::PropertyType::UInt8, val)); + SetCustomTypeValue(key, valueName, ApplicationDataRegistryType_UInt8, val); break; } case winrt::Windows::Foundation::PropertyType::Int16: { auto val{ winrt::unbox_value(value) }; - wil::reg::set_value_binary(key, valueName, REG_BINARY, TaggedBytes(winrt::Windows::Foundation::PropertyType::Int16, val)); + SetCustomTypeValue(key, valueName, ApplicationDataRegistryType_Int16, val); break; } case winrt::Windows::Foundation::PropertyType::UInt16: { auto val{ winrt::unbox_value(value) }; - wil::reg::set_value_binary(key, valueName, REG_BINARY, TaggedBytes(winrt::Windows::Foundation::PropertyType::UInt16, val)); + SetCustomTypeValue(key, valueName, ApplicationDataRegistryType_UInt16, val); break; } case winrt::Windows::Foundation::PropertyType::Int32: { auto val{ winrt::unbox_value(value) }; - wil::reg::set_value_binary(key, valueName, REG_BINARY, TaggedBytes(winrt::Windows::Foundation::PropertyType::Int32, val)); + SetCustomTypeValue(key, valueName, ApplicationDataRegistryType_Int32, val); break; } case winrt::Windows::Foundation::PropertyType::Int64: { auto val{ winrt::unbox_value(value) }; - wil::reg::set_value_binary(key, valueName, REG_BINARY, TaggedBytes(winrt::Windows::Foundation::PropertyType::Int64, val)); + SetCustomTypeValue(key, valueName, ApplicationDataRegistryType_Int64, val); break; } case winrt::Windows::Foundation::PropertyType::Single: { auto val{ winrt::unbox_value(value) }; - wil::reg::set_value_binary(key, valueName, REG_BINARY, TaggedBytes(winrt::Windows::Foundation::PropertyType::Single, val)); + SetCustomTypeValue(key, valueName, ApplicationDataRegistryType_Single, val); break; } case winrt::Windows::Foundation::PropertyType::Double: { auto val{ winrt::unbox_value(value) }; - wil::reg::set_value_binary(key, valueName, REG_BINARY, TaggedBytes(winrt::Windows::Foundation::PropertyType::Double, val)); + SetCustomTypeValue(key, valueName, ApplicationDataRegistryType_Double, val); break; } case winrt::Windows::Foundation::PropertyType::Char16: { auto val{ winrt::unbox_value(value) }; - wil::reg::set_value_binary(key, valueName, REG_BINARY, TaggedBytes(winrt::Windows::Foundation::PropertyType::Char16, val)); + SetCustomTypeValue(key, valueName, ApplicationDataRegistryType_Char16, val); break; } case winrt::Windows::Foundation::PropertyType::Boolean: { auto val{ winrt::unbox_value(value) }; uint8_t byte{ val ? static_cast(1) : static_cast(0) }; - wil::reg::set_value_binary(key, valueName, REG_BINARY, TaggedBytes(winrt::Windows::Foundation::PropertyType::Boolean, byte)); + SetCustomTypeValue(key, valueName, ApplicationDataRegistryType_Boolean, byte); break; } case winrt::Windows::Foundation::PropertyType::DateTime: { auto val{ propertyValue.GetDateTime() }; auto ticks{ val.time_since_epoch().count() }; - wil::reg::set_value_binary(key, valueName, REG_BINARY, TaggedBytes(winrt::Windows::Foundation::PropertyType::DateTime, ticks)); + SetCustomTypeValue(key, valueName, ApplicationDataRegistryType_DateTime, ticks); break; } case winrt::Windows::Foundation::PropertyType::TimeSpan: { auto val{ propertyValue.GetTimeSpan() }; auto duration{ val.count() }; - wil::reg::set_value_binary(key, valueName, REG_BINARY, TaggedBytes(winrt::Windows::Foundation::PropertyType::TimeSpan, duration)); + SetCustomTypeValue(key, valueName, ApplicationDataRegistryType_TimeSpan, duration); break; } case winrt::Windows::Foundation::PropertyType::Guid: { auto val{ propertyValue.GetGuid() }; - wil::reg::set_value_binary(key, valueName, REG_BINARY, TaggedBytes(winrt::Windows::Foundation::PropertyType::Guid, val)); + SetCustomTypeValue(key, valueName, ApplicationDataRegistryType_Guid, val); break; } case winrt::Windows::Foundation::PropertyType::Point: { auto val{ propertyValue.GetPoint() }; - wil::reg::set_value_binary(key, valueName, REG_BINARY, TaggedBytes(winrt::Windows::Foundation::PropertyType::Point, val)); + SetCustomTypeValue(key, valueName, ApplicationDataRegistryType_Point, val); break; } case winrt::Windows::Foundation::PropertyType::Size: { auto val{ propertyValue.GetSize() }; - wil::reg::set_value_binary(key, valueName, REG_BINARY, TaggedBytes(winrt::Windows::Foundation::PropertyType::Size, val)); + SetCustomTypeValue(key, valueName, ApplicationDataRegistryType_Size, val); break; } case winrt::Windows::Foundation::PropertyType::Rect: { auto val{ propertyValue.GetRect() }; - wil::reg::set_value_binary(key, valueName, REG_BINARY, TaggedBytes(winrt::Windows::Foundation::PropertyType::Rect, val)); + SetCustomTypeValue(key, valueName, ApplicationDataRegistryType_Rect, val); + break; + } + case winrt::Windows::Foundation::PropertyType::UInt8Array: + { + winrt::com_array arr; + propertyValue.GetUInt8Array(arr); + SetCustomTypeArrayValue(key, valueName, ApplicationDataRegistryType_UInt8Array, arr.data(), arr.size()); + break; + } + case winrt::Windows::Foundation::PropertyType::Int16Array: + { + winrt::com_array arr; + propertyValue.GetInt16Array(arr); + SetCustomTypeArrayValue(key, valueName, ApplicationDataRegistryType_Int16Array, arr.data(), arr.size()); + break; + } + case winrt::Windows::Foundation::PropertyType::UInt16Array: + { + winrt::com_array arr; + propertyValue.GetUInt16Array(arr); + SetCustomTypeArrayValue(key, valueName, ApplicationDataRegistryType_UInt16Array, arr.data(), arr.size()); + break; + } + case winrt::Windows::Foundation::PropertyType::Int32Array: + { + winrt::com_array arr; + propertyValue.GetInt32Array(arr); + SetCustomTypeArrayValue(key, valueName, ApplicationDataRegistryType_Int32Array, arr.data(), arr.size()); + break; + } + case winrt::Windows::Foundation::PropertyType::UInt32Array: + { + winrt::com_array arr; + propertyValue.GetUInt32Array(arr); + SetCustomTypeArrayValue(key, valueName, ApplicationDataRegistryType_UInt32Array, arr.data(), arr.size()); + break; + } + case winrt::Windows::Foundation::PropertyType::Int64Array: + { + winrt::com_array arr; + propertyValue.GetInt64Array(arr); + SetCustomTypeArrayValue(key, valueName, ApplicationDataRegistryType_Int64Array, arr.data(), arr.size()); + break; + } + case winrt::Windows::Foundation::PropertyType::UInt64Array: + { + winrt::com_array arr; + propertyValue.GetUInt64Array(arr); + SetCustomTypeArrayValue(key, valueName, ApplicationDataRegistryType_UInt64Array, arr.data(), arr.size()); + break; + } + case winrt::Windows::Foundation::PropertyType::SingleArray: + { + winrt::com_array arr; + propertyValue.GetSingleArray(arr); + SetCustomTypeArrayValue(key, valueName, ApplicationDataRegistryType_SingleArray, arr.data(), arr.size()); + break; + } + case winrt::Windows::Foundation::PropertyType::DoubleArray: + { + winrt::com_array arr; + propertyValue.GetDoubleArray(arr); + SetCustomTypeArrayValue(key, valueName, ApplicationDataRegistryType_DoubleArray, arr.data(), arr.size()); + break; + } + case winrt::Windows::Foundation::PropertyType::Char16Array: + { + winrt::com_array arr; + propertyValue.GetChar16Array(arr); + SetCustomTypeArrayValue(key, valueName, ApplicationDataRegistryType_Char16Array, arr.data(), arr.size()); + break; + } + case winrt::Windows::Foundation::PropertyType::BooleanArray: + { + winrt::com_array arr; + propertyValue.GetBooleanArray(arr); + std::vector bytes(arr.size()); + for (uint32_t i = 0; i < arr.size(); ++i) + { + bytes[i] = arr[i] ? static_cast(1) : static_cast(0); + } + THROW_IF_WIN32_ERROR(::RegSetValueExW( + key, valueName, 0, ApplicationDataRegistryType_BooleanArray, + bytes.data(), static_cast(bytes.size()))); + break; + } + case winrt::Windows::Foundation::PropertyType::StringArray: + { + winrt::com_array arr; + propertyValue.GetStringArray(arr); + std::wstring multiSz; + for (const auto& s : arr) + { + multiSz.append(s.c_str(), s.size()); + multiSz.push_back(L'\0'); + } + multiSz.push_back(L'\0'); + THROW_IF_WIN32_ERROR(::RegSetValueExW( + key, valueName, 0, REG_MULTI_SZ, + reinterpret_cast(multiSz.c_str()), + static_cast(multiSz.size() * sizeof(wchar_t)))); + break; + } + case winrt::Windows::Foundation::PropertyType::DateTimeArray: + { + winrt::com_array arr; + propertyValue.GetDateTimeArray(arr); + std::vector ticks(arr.size()); + for (uint32_t i = 0; i < arr.size(); ++i) + { + ticks[i] = arr[i].time_since_epoch().count(); + } + SetCustomTypeArrayValue(key, valueName, ApplicationDataRegistryType_DateTimeArray, ticks.data(), static_cast(ticks.size())); + break; + } + case winrt::Windows::Foundation::PropertyType::TimeSpanArray: + { + winrt::com_array arr; + propertyValue.GetTimeSpanArray(arr); + std::vector durations(arr.size()); + for (uint32_t i = 0; i < arr.size(); ++i) + { + durations[i] = arr[i].count(); + } + SetCustomTypeArrayValue(key, valueName, ApplicationDataRegistryType_TimeSpanArray, durations.data(), static_cast(durations.size())); + break; + } + case winrt::Windows::Foundation::PropertyType::GuidArray: + { + winrt::com_array arr; + propertyValue.GetGuidArray(arr); + SetCustomTypeArrayValue(key, valueName, ApplicationDataRegistryType_GuidArray, arr.data(), arr.size()); + break; + } + case winrt::Windows::Foundation::PropertyType::PointArray: + { + winrt::com_array arr; + propertyValue.GetPointArray(arr); + SetCustomTypeArrayValue(key, valueName, ApplicationDataRegistryType_PointArray, arr.data(), arr.size()); + break; + } + case winrt::Windows::Foundation::PropertyType::SizeArray: + { + winrt::com_array arr; + propertyValue.GetSizeArray(arr); + SetCustomTypeArrayValue(key, valueName, ApplicationDataRegistryType_SizeArray, arr.data(), arr.size()); + break; + } + case winrt::Windows::Foundation::PropertyType::RectArray: + { + winrt::com_array arr; + propertyValue.GetRectArray(arr); + SetCustomTypeArrayValue(key, valueName, ApplicationDataRegistryType_RectArray, arr.data(), arr.size()); break; } default: diff --git a/test/ApplicationData/UnpackagedApplicationDataTests.cpp b/test/ApplicationData/UnpackagedApplicationDataTests.cpp index b390ef5c58..68c3862675 100644 --- a/test/ApplicationData/UnpackagedApplicationDataTests.cpp +++ b/test/ApplicationData/UnpackagedApplicationDataTests.cpp @@ -50,6 +50,14 @@ namespace Test::ApplicationData::Tests } void CreateResources() + { + CreateResources_FileSystem(); + // Registry resources are created by the code under test, so no need to create them here + // But delete any pre-existing in case a previous test crashed or otherwise didn't complete and clean up properly + DeleteResources_Registry(); + } + + void CreateResources_FileSystem() { const auto localPath{ UserLocalPath(Publisher, Product) }; VERIFY_SUCCEEDED(wil::CreateDirectoryDeepNoThrow(localPath.c_str()), @@ -61,6 +69,12 @@ namespace Test::ApplicationData::Tests } void DeleteResources() + { + DeleteResources_FileSystem(); + DeleteResources_Registry(); + } + + void DeleteResources_FileSystem() { const std::filesystem::path temporaryPath{ UserTemporaryPath(Publisher).c_str() }; if (PathExists(temporaryPath)) @@ -79,6 +93,16 @@ namespace Test::ApplicationData::Tests } } + void DeleteResources_Registry() + { + PCWSTR c_product{ L"Software\\TestApplicationData_Contoso" }; + const auto hr{ HRESULT_FROM_WIN32(::RegDeleteTreeW(HKEY_CURRENT_USER, c_product)) }; + if (!wil::reg::is_registry_not_found(hr)) + { + VERIFY_SUCCEEDED(hr, WEX::Common::String().Format(L"RegDeleteTreeW HKEY_CURRENT_USER\\%s", c_product)); + } + } + TEST_METHOD(GetForUnpackaged_InvalidParameter) { const winrt::hstring publisher{ L"FabrikamTest" }; @@ -457,5 +481,645 @@ namespace Test::ApplicationData::Tests VERIFY_ARE_EQUAL(winrt::Windows::Foundation::AsyncStatus::Error, asyncAction.Status()); VERIFY_ARE_EQUAL(E_NOTIMPL, asyncAction.ErrorCode()); } + + TEST_METHOD(ContainerOperations) + { + auto applicationData{ winrt::Microsoft::Windows::Storage::ApplicationData::GetForUnpackaged(L"TestApplicationData_Contoso", L"SupermarketPointOfSale") }; + VERIFY_IS_NOT_NULL(applicationData); + + auto localSettings{ applicationData.LocalSettings() }; + VERIFY_IS_NOT_NULL(localSettings); + VERIFY_ARE_EQUAL(L"", localSettings.Name()); + VERIFY_ARE_EQUAL(winrt::Microsoft::Windows::Storage::ApplicationDataLocality::Local, localSettings.Locality()); + + // Start clean + auto containers{ localSettings.Containers() }; + if (containers.HasKey(L"Inventory")) + { + localSettings.DeleteContainer(L"Inventory"); + } + containers = localSettings.Containers(); + VERIFY_IS_FALSE(containers.HasKey(L"Inventory")); + + // CreateContainer with Always creates a new container + auto inventory{ localSettings.CreateContainer(L"Inventory", winrt::Microsoft::Windows::Storage::ApplicationDataCreateDisposition::Always) }; + VERIFY_IS_NOT_NULL(inventory); + VERIFY_ARE_EQUAL(L"Inventory", inventory.Name()); + VERIFY_ARE_EQUAL(winrt::Microsoft::Windows::Storage::ApplicationDataLocality::Local, inventory.Locality()); + + containers = localSettings.Containers(); + VERIFY_ARE_EQUAL(1u, containers.Size()); + VERIFY_IS_TRUE(containers.HasKey(L"Inventory")); + auto lookedUp{ containers.Lookup(L"Inventory") }; + VERIFY_IS_NOT_NULL(lookedUp); + VERIFY_ARE_EQUAL(L"Inventory", lookedUp.Name()); + + // CreateContainer with Always on existing container returns it + auto inventoryAgain{ localSettings.CreateContainer(L"Inventory", winrt::Microsoft::Windows::Storage::ApplicationDataCreateDisposition::Always) }; + VERIFY_IS_NOT_NULL(inventoryAgain); + VERIFY_ARE_EQUAL(L"Inventory", inventoryAgain.Name()); + containers = localSettings.Containers(); + VERIFY_ARE_EQUAL(1u, containers.Size()); + + // CreateContainer with Existing on existing container returns it + auto inventoryExisting{ localSettings.CreateContainer(L"Inventory", winrt::Microsoft::Windows::Storage::ApplicationDataCreateDisposition::Existing) }; + VERIFY_IS_NOT_NULL(inventoryExisting); + VERIFY_ARE_EQUAL(L"Inventory", inventoryExisting.Name()); + + // Multiple containers + auto pricing{ localSettings.CreateContainer(L"Pricing", winrt::Microsoft::Windows::Storage::ApplicationDataCreateDisposition::Always) }; + containers = localSettings.Containers(); + VERIFY_ARE_EQUAL(2u, containers.Size()); + VERIFY_IS_TRUE(containers.HasKey(L"Inventory")); + VERIFY_IS_TRUE(containers.HasKey(L"Pricing")); + + // Values in a child container + auto inventoryValues{ inventory.Values() }; + VERIFY_ARE_EQUAL(0u, inventoryValues.Size()); + inventoryValues.Insert(L"SKU", winrt::box_value(L"ABC-123")); + VERIFY_ARE_EQUAL(1u, inventoryValues.Size()); + auto sku{ winrt::unbox_value(inventoryValues.Lookup(L"SKU")) }; + VERIFY_ARE_EQUAL(L"ABC-123", sku); + + // DeleteContainer removes the container and its values + localSettings.DeleteContainer(L"Inventory"); + containers = localSettings.Containers(); + VERIFY_ARE_EQUAL(1u, containers.Size()); + VERIFY_IS_FALSE(containers.HasKey(L"Inventory")); + VERIFY_IS_TRUE(containers.HasKey(L"Pricing")); + + // Cleanup + localSettings.DeleteContainer(L"Pricing"); + containers = localSettings.Containers(); + VERIFY_ARE_EQUAL(0u, containers.Size()); + } + + TEST_METHOD(Values_ScalarTypes) + { + auto applicationData{ winrt::Microsoft::Windows::Storage::ApplicationData::GetForUnpackaged(L"TestApplicationData_Contoso", L"SupermarketPointOfSale") }; + auto localSettings{ applicationData.LocalSettings() }; + auto values{ localSettings.Values() }; + values.Clear(); + + // Empty (null) + values.Insert(L"Empty", nullptr); + VERIFY_IS_TRUE(values.HasKey(L"Empty")); + auto emptyVal{ values.Lookup(L"Empty") }; + VERIFY_IS_NULL(emptyVal); + + // String + values.Insert(L"String", winrt::box_value(L"Hello World")); + VERIFY_ARE_EQUAL(L"Hello World", winrt::unbox_value(values.Lookup(L"String"))); + + // UInt8 + values.Insert(L"UInt8", winrt::box_value(static_cast(42))); + VERIFY_ARE_EQUAL(static_cast(42), winrt::unbox_value(values.Lookup(L"UInt8"))); + + // Int16 + values.Insert(L"Int16", winrt::box_value(static_cast(-1234))); + VERIFY_ARE_EQUAL(static_cast(-1234), winrt::unbox_value(values.Lookup(L"Int16"))); + + // UInt16 + values.Insert(L"UInt16", winrt::box_value(static_cast(65535))); + VERIFY_ARE_EQUAL(static_cast(65535), winrt::unbox_value(values.Lookup(L"UInt16"))); + + // Int32 + values.Insert(L"Int32", winrt::box_value(static_cast(-100000))); + VERIFY_ARE_EQUAL(static_cast(-100000), winrt::unbox_value(values.Lookup(L"Int32"))); + + // UInt32 + values.Insert(L"UInt32", winrt::box_value(static_cast(3000000000u))); + VERIFY_ARE_EQUAL(static_cast(3000000000u), winrt::unbox_value(values.Lookup(L"UInt32"))); + + // Int64 + values.Insert(L"Int64", winrt::box_value(static_cast(-9876543210LL))); + VERIFY_ARE_EQUAL(static_cast(-9876543210LL), winrt::unbox_value(values.Lookup(L"Int64"))); + + // UInt64 + values.Insert(L"UInt64", winrt::box_value(static_cast(18000000000000000000ULL))); + VERIFY_ARE_EQUAL(static_cast(18000000000000000000ULL), winrt::unbox_value(values.Lookup(L"UInt64"))); + + // Single + values.Insert(L"Single", winrt::box_value(3.14f)); + VERIFY_ARE_EQUAL(3.14f, winrt::unbox_value(values.Lookup(L"Single"))); + + // Double + values.Insert(L"Double", winrt::box_value(2.718281828)); + VERIFY_ARE_EQUAL(2.718281828, winrt::unbox_value(values.Lookup(L"Double"))); + + // Char16 + values.Insert(L"Char16", winrt::box_value(L'Z')); + VERIFY_ARE_EQUAL(L'Z', winrt::unbox_value(values.Lookup(L"Char16"))); + + // Boolean true + values.Insert(L"BoolTrue", winrt::box_value(true)); + VERIFY_IS_TRUE(winrt::unbox_value(values.Lookup(L"BoolTrue"))); + + // Boolean false + values.Insert(L"BoolFalse", winrt::box_value(false)); + VERIFY_IS_FALSE(winrt::unbox_value(values.Lookup(L"BoolFalse"))); + + // DateTime + const winrt::Windows::Foundation::DateTime dateTime{ winrt::Windows::Foundation::TimeSpan{ 132800000000000000LL } }; + values.Insert(L"DateTime", winrt::box_value(dateTime)); + auto readDateTime{ winrt::unbox_value(values.Lookup(L"DateTime")) }; + VERIFY_ARE_EQUAL(dateTime.time_since_epoch().count(), readDateTime.time_since_epoch().count()); + + // TimeSpan + const winrt::Windows::Foundation::TimeSpan timeSpan{ 36000000000LL }; // 1 hour in 100ns ticks + values.Insert(L"TimeSpan", winrt::box_value(timeSpan)); + auto readTimeSpan{ winrt::unbox_value(values.Lookup(L"TimeSpan")) }; + VERIFY_ARE_EQUAL(timeSpan.count(), readTimeSpan.count()); + + // Guid + const winrt::guid testGuid{ 0x01020304, 0x0506, 0x0708, { 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10 } }; + values.Insert(L"Guid", winrt::box_value(testGuid)); + VERIFY_ARE_EQUAL(testGuid, winrt::unbox_value(values.Lookup(L"Guid"))); + + // Point + const winrt::Windows::Foundation::Point point{ 1.5f, 2.5f }; + values.Insert(L"Point", winrt::box_value(point)); + auto readPoint{ winrt::unbox_value(values.Lookup(L"Point")) }; + VERIFY_ARE_EQUAL(point.X, readPoint.X); + VERIFY_ARE_EQUAL(point.Y, readPoint.Y); + + // Size + const winrt::Windows::Foundation::Size size{ 100.0f, 200.0f }; + values.Insert(L"Size", winrt::box_value(size)); + auto readSize{ winrt::unbox_value(values.Lookup(L"Size")) }; + VERIFY_ARE_EQUAL(size.Width, readSize.Width); + VERIFY_ARE_EQUAL(size.Height, readSize.Height); + + // Rect + const winrt::Windows::Foundation::Rect rect{ 10.0f, 20.0f, 300.0f, 400.0f }; + values.Insert(L"Rect", winrt::box_value(rect)); + auto readRect{ winrt::unbox_value(values.Lookup(L"Rect")) }; + VERIFY_ARE_EQUAL(rect.X, readRect.X); + VERIFY_ARE_EQUAL(rect.Y, readRect.Y); + VERIFY_ARE_EQUAL(rect.Width, readRect.Width); + VERIFY_ARE_EQUAL(rect.Height, readRect.Height); + + // Overwrite a value + values.Insert(L"String", winrt::box_value(L"Updated")); + VERIFY_ARE_EQUAL(L"Updated", winrt::unbox_value(values.Lookup(L"String"))); + + // Verify count + VERIFY_ARE_EQUAL(20u, values.Size()); + + values.Clear(); + } + + TEST_METHOD(Values_ArrayTypes) + { + auto applicationData{ winrt::Microsoft::Windows::Storage::ApplicationData::GetForUnpackaged(L"TestApplicationData_Contoso", L"SupermarketPointOfSale") }; + auto localSettings{ applicationData.LocalSettings() }; + auto values{ localSettings.Values() }; + values.Clear(); + + namespace PV = winrt::Windows::Foundation::PropertyValue; + namespace WF = winrt::Windows::Foundation; + + // UInt8Array + { + winrt::com_array arr{ 1, 2, 3, 255 }; + values.Insert(L"UInt8Array", PV::CreateUInt8Array(arr)); + auto pv{ values.Lookup(L"UInt8Array").as() }; + winrt::com_array result; + pv.GetUInt8Array(result); + VERIFY_ARE_EQUAL(4u, result.size()); + VERIFY_ARE_EQUAL(static_cast(1), result[0]); + VERIFY_ARE_EQUAL(static_cast(255), result[3]); + } + + // Int16Array + { + winrt::com_array arr{ -32768, 0, 32767 }; + values.Insert(L"Int16Array", PV::CreateInt16Array(arr)); + auto pv{ values.Lookup(L"Int16Array").as() }; + winrt::com_array result; + pv.GetInt16Array(result); + VERIFY_ARE_EQUAL(3u, result.size()); + VERIFY_ARE_EQUAL(static_cast(-32768), result[0]); + VERIFY_ARE_EQUAL(static_cast(32767), result[2]); + } + + // UInt16Array + { + winrt::com_array arr{ 0, 1000, 65535 }; + values.Insert(L"UInt16Array", PV::CreateUInt16Array(arr)); + auto pv{ values.Lookup(L"UInt16Array").as() }; + winrt::com_array result; + pv.GetUInt16Array(result); + VERIFY_ARE_EQUAL(3u, result.size()); + VERIFY_ARE_EQUAL(static_cast(0), result[0]); + VERIFY_ARE_EQUAL(static_cast(65535), result[2]); + } + + // Int32Array + { + winrt::com_array arr{ -2147483647 - 1, 0, 2147483647 }; + values.Insert(L"Int32Array", PV::CreateInt32Array(arr)); + auto pv{ values.Lookup(L"Int32Array").as() }; + winrt::com_array result; + pv.GetInt32Array(result); + VERIFY_ARE_EQUAL(3u, result.size()); + VERIFY_ARE_EQUAL(static_cast(-2147483647 - 1), result[0]); + VERIFY_ARE_EQUAL(static_cast(2147483647), result[2]); + } + + // UInt32Array + { + winrt::com_array arr{ 0u, 1000000u, 4294967295u }; + values.Insert(L"UInt32Array", PV::CreateUInt32Array(arr)); + auto pv{ values.Lookup(L"UInt32Array").as() }; + winrt::com_array result; + pv.GetUInt32Array(result); + VERIFY_ARE_EQUAL(3u, result.size()); + VERIFY_ARE_EQUAL(0u, result[0]); + VERIFY_ARE_EQUAL(4294967295u, result[2]); + } + + // Int64Array + { + winrt::com_array arr{ -9223372036854775807LL - 1, 0LL, 9223372036854775807LL }; + values.Insert(L"Int64Array", PV::CreateInt64Array(arr)); + auto pv{ values.Lookup(L"Int64Array").as() }; + winrt::com_array result; + pv.GetInt64Array(result); + VERIFY_ARE_EQUAL(3u, result.size()); + VERIFY_ARE_EQUAL(static_cast(-9223372036854775807LL - 1), result[0]); + VERIFY_ARE_EQUAL(static_cast(9223372036854775807LL), result[2]); + } + + // UInt64Array + { + winrt::com_array arr{ 0ULL, 18446744073709551615ULL }; + values.Insert(L"UInt64Array", PV::CreateUInt64Array(arr)); + auto pv{ values.Lookup(L"UInt64Array").as() }; + winrt::com_array result; + pv.GetUInt64Array(result); + VERIFY_ARE_EQUAL(2u, result.size()); + VERIFY_ARE_EQUAL(0ULL, result[0]); + VERIFY_ARE_EQUAL(18446744073709551615ULL, result[1]); + } + + // SingleArray + { + winrt::com_array arr{ 1.1f, 2.2f, 3.3f }; + values.Insert(L"SingleArray", PV::CreateSingleArray(arr)); + auto pv{ values.Lookup(L"SingleArray").as() }; + winrt::com_array result; + pv.GetSingleArray(result); + VERIFY_ARE_EQUAL(3u, result.size()); + VERIFY_ARE_EQUAL(1.1f, result[0]); + VERIFY_ARE_EQUAL(3.3f, result[2]); + } + + // DoubleArray + { + winrt::com_array arr{ 1.11, 2.22 }; + values.Insert(L"DoubleArray", PV::CreateDoubleArray(arr)); + auto pv{ values.Lookup(L"DoubleArray").as() }; + winrt::com_array result; + pv.GetDoubleArray(result); + VERIFY_ARE_EQUAL(2u, result.size()); + VERIFY_ARE_EQUAL(1.11, result[0]); + VERIFY_ARE_EQUAL(2.22, result[1]); + } + + // Char16Array + { + winrt::com_array arr{ L'A', L'B', L'\x4e16' }; + values.Insert(L"Char16Array", PV::CreateChar16Array(arr)); + auto pv{ values.Lookup(L"Char16Array").as() }; + winrt::com_array result; + pv.GetChar16Array(result); + VERIFY_ARE_EQUAL(3u, result.size()); + VERIFY_ARE_EQUAL(L'A', result[0]); + VERIFY_ARE_EQUAL(L'\x4e16', result[2]); + } + + // BooleanArray + { + winrt::com_array arr{ true, false, true, false }; + values.Insert(L"BooleanArray", PV::CreateBooleanArray(arr)); + auto pv{ values.Lookup(L"BooleanArray").as() }; + winrt::com_array result; + pv.GetBooleanArray(result); + VERIFY_ARE_EQUAL(4u, result.size()); + VERIFY_IS_TRUE(result[0]); + VERIFY_IS_FALSE(result[1]); + VERIFY_IS_TRUE(result[2]); + VERIFY_IS_FALSE(result[3]); + } + + // StringArray + { + winrt::com_array arr{ winrt::hstring{L"Alpha"}, winrt::hstring{L"Beta"}, winrt::hstring{L"Gamma"} }; + values.Insert(L"StringArray", PV::CreateStringArray(arr)); + auto pv{ values.Lookup(L"StringArray").as() }; + winrt::com_array result; + pv.GetStringArray(result); + VERIFY_ARE_EQUAL(3u, result.size()); + VERIFY_ARE_EQUAL(winrt::hstring{L"Alpha"}, result[0]); + VERIFY_ARE_EQUAL(winrt::hstring{L"Beta"}, result[1]); + VERIFY_ARE_EQUAL(winrt::hstring{L"Gamma"}, result[2]); + } + + // DateTimeArray + { + WF::DateTime dt1{ WF::TimeSpan{ 100000000LL } }; + WF::DateTime dt2{ WF::TimeSpan{ 200000000LL } }; + winrt::com_array arr{ dt1, dt2 }; + values.Insert(L"DateTimeArray", PV::CreateDateTimeArray(arr)); + auto pv{ values.Lookup(L"DateTimeArray").as() }; + winrt::com_array result; + pv.GetDateTimeArray(result); + VERIFY_ARE_EQUAL(2u, result.size()); + VERIFY_ARE_EQUAL(dt1.time_since_epoch().count(), result[0].time_since_epoch().count()); + VERIFY_ARE_EQUAL(dt2.time_since_epoch().count(), result[1].time_since_epoch().count()); + } + + // TimeSpanArray + { + WF::TimeSpan ts1{ 10000000LL }; // 1 second + WF::TimeSpan ts2{ 600000000LL }; // 1 minute + winrt::com_array arr{ ts1, ts2 }; + values.Insert(L"TimeSpanArray", PV::CreateTimeSpanArray(arr)); + auto pv{ values.Lookup(L"TimeSpanArray").as() }; + winrt::com_array result; + pv.GetTimeSpanArray(result); + VERIFY_ARE_EQUAL(2u, result.size()); + VERIFY_ARE_EQUAL(ts1.count(), result[0].count()); + VERIFY_ARE_EQUAL(ts2.count(), result[1].count()); + } + + // GuidArray + { + winrt::guid g1{ 0x11111111, 0x2222, 0x3333, { 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xAA, 0xBB } }; + winrt::guid g2{ 0xAAAAAAAA, 0xBBBB, 0xCCCC, { 0xDD, 0xEE, 0xFF, 0x00, 0x11, 0x22, 0x33, 0x44 } }; + winrt::com_array arr{ g1, g2 }; + values.Insert(L"GuidArray", PV::CreateGuidArray(arr)); + auto pv{ values.Lookup(L"GuidArray").as() }; + winrt::com_array result; + pv.GetGuidArray(result); + VERIFY_ARE_EQUAL(2u, result.size()); + VERIFY_ARE_EQUAL(g1, result[0]); + VERIFY_ARE_EQUAL(g2, result[1]); + } + + // PointArray + { + WF::Point p1{ 1.0f, 2.0f }; + WF::Point p2{ 3.0f, 4.0f }; + winrt::com_array arr{ p1, p2 }; + values.Insert(L"PointArray", PV::CreatePointArray(arr)); + auto pv{ values.Lookup(L"PointArray").as() }; + winrt::com_array result; + pv.GetPointArray(result); + VERIFY_ARE_EQUAL(2u, result.size()); + VERIFY_ARE_EQUAL(p1.X, result[0].X); + VERIFY_ARE_EQUAL(p1.Y, result[0].Y); + VERIFY_ARE_EQUAL(p2.X, result[1].X); + VERIFY_ARE_EQUAL(p2.Y, result[1].Y); + } + + // SizeArray + { + WF::Size s1{ 10.0f, 20.0f }; + WF::Size s2{ 30.0f, 40.0f }; + winrt::com_array arr{ s1, s2 }; + values.Insert(L"SizeArray", PV::CreateSizeArray(arr)); + auto pv{ values.Lookup(L"SizeArray").as() }; + winrt::com_array result; + pv.GetSizeArray(result); + VERIFY_ARE_EQUAL(2u, result.size()); + VERIFY_ARE_EQUAL(s1.Width, result[0].Width); + VERIFY_ARE_EQUAL(s1.Height, result[0].Height); + VERIFY_ARE_EQUAL(s2.Width, result[1].Width); + VERIFY_ARE_EQUAL(s2.Height, result[1].Height); + } + + // RectArray + { + WF::Rect r1{ 1.0f, 2.0f, 3.0f, 4.0f }; + WF::Rect r2{ 5.0f, 6.0f, 7.0f, 8.0f }; + winrt::com_array arr{ r1, r2 }; + values.Insert(L"RectArray", PV::CreateRectArray(arr)); + auto pv{ values.Lookup(L"RectArray").as() }; + winrt::com_array result; + pv.GetRectArray(result); + VERIFY_ARE_EQUAL(2u, result.size()); + VERIFY_ARE_EQUAL(r1.X, result[0].X); + VERIFY_ARE_EQUAL(r1.Y, result[0].Y); + VERIFY_ARE_EQUAL(r1.Width, result[0].Width); + VERIFY_ARE_EQUAL(r1.Height, result[0].Height); + VERIFY_ARE_EQUAL(r2.X, result[1].X); + VERIFY_ARE_EQUAL(r2.Y, result[1].Y); + VERIFY_ARE_EQUAL(r2.Width, result[1].Width); + VERIFY_ARE_EQUAL(r2.Height, result[1].Height); + } + + values.Clear(); + } + + TEST_METHOD(Values_PropertySetOperations) + { + auto applicationData{ winrt::Microsoft::Windows::Storage::ApplicationData::GetForUnpackaged(L"TestApplicationData_Contoso", L"SupermarketPointOfSale") }; + auto localSettings{ applicationData.LocalSettings() }; + auto values{ localSettings.Values() }; + values.Clear(); + + // Insert and Size + VERIFY_ARE_EQUAL(0u, values.Size()); + auto inserted{ values.Insert(L"Key1", winrt::box_value(L"Value1")) }; + VERIFY_IS_FALSE(inserted); // Insert returns true if key existed (replaced) + VERIFY_ARE_EQUAL(1u, values.Size()); + + values.Insert(L"Key2", winrt::box_value(static_cast(42))); + VERIFY_ARE_EQUAL(2u, values.Size()); + + // Replace existing key + inserted = values.Insert(L"Key1", winrt::box_value(L"Updated")); + VERIFY_IS_TRUE(inserted); // Key existed, so replaced + VERIFY_ARE_EQUAL(2u, values.Size()); + VERIFY_ARE_EQUAL(L"Updated", winrt::unbox_value(values.Lookup(L"Key1"))); + + // HasKey + VERIFY_IS_TRUE(values.HasKey(L"Key1")); + VERIFY_IS_TRUE(values.HasKey(L"Key2")); + VERIFY_IS_FALSE(values.HasKey(L"NonExistent")); + + // Lookup + auto val1{ values.Lookup(L"Key1") }; + VERIFY_IS_NOT_NULL(val1); + auto val2{ values.Lookup(L"Key2") }; + VERIFY_IS_NOT_NULL(val2); + VERIFY_ARE_EQUAL(static_cast(42), winrt::unbox_value(val2)); + + // Remove + values.Remove(L"Key2"); + VERIFY_ARE_EQUAL(1u, values.Size()); + VERIFY_IS_FALSE(values.HasKey(L"Key2")); + VERIFY_IS_TRUE(values.HasKey(L"Key1")); + + // GetView + values.Insert(L"ViewKey", winrt::box_value(L"ViewValue")); + auto view{ values.GetView() }; + VERIFY_ARE_EQUAL(2u, view.Size()); + VERIFY_IS_TRUE(view.HasKey(L"Key1")); + VERIFY_IS_TRUE(view.HasKey(L"ViewKey")); + + // First / iteration + auto iter{ values.First() }; + VERIFY_IS_TRUE(iter.HasCurrent()); + uint32_t iterCount{ 0 }; + while (iter.HasCurrent()) + { + auto kvp{ iter.Current() }; + VERIFY_IS_TRUE(values.HasKey(kvp.Key())); + ++iterCount; + iter.MoveNext(); + } + VERIFY_ARE_EQUAL(2u, iterCount); + + // Clear + values.Clear(); + VERIFY_ARE_EQUAL(0u, values.Size()); + VERIFY_IS_FALSE(values.HasKey(L"Key1")); + VERIFY_IS_FALSE(values.HasKey(L"ViewKey")); + } + + TEST_METHOD(Values_MapChangedEvent) + { + auto applicationData{ winrt::Microsoft::Windows::Storage::ApplicationData::GetForUnpackaged(L"TestApplicationData_Contoso", L"SupermarketPointOfSale") }; + auto localSettings{ applicationData.LocalSettings() }; + auto values{ localSettings.Values() }; + values.Clear(); + + auto observableMap{ values.as>() }; + VERIFY_IS_NOT_NULL(observableMap); + + winrt::Windows::Foundation::Collections::CollectionChange lastChange{}; + winrt::hstring lastKey; + int callCount{ 0 }; + + auto token{ observableMap.MapChanged([&](auto const&, auto const& args) + { + lastChange = args.CollectionChange(); + lastKey = args.Key(); + ++callCount; + }) }; + + // Insert fires ItemInserted + values.Insert(L"EventKey", winrt::box_value(L"EventValue")); + VERIFY_ARE_EQUAL(1, callCount); + VERIFY_ARE_EQUAL(winrt::Windows::Foundation::Collections::CollectionChange::ItemInserted, lastChange); + VERIFY_ARE_EQUAL(L"EventKey", lastKey); + + // Replace fires ItemChanged + values.Insert(L"EventKey", winrt::box_value(L"UpdatedValue")); + VERIFY_ARE_EQUAL(2, callCount); + VERIFY_ARE_EQUAL(winrt::Windows::Foundation::Collections::CollectionChange::ItemChanged, lastChange); + VERIFY_ARE_EQUAL(L"EventKey", lastKey); + + // Remove fires ItemRemoved + values.Remove(L"EventKey"); + VERIFY_ARE_EQUAL(3, callCount); + VERIFY_ARE_EQUAL(winrt::Windows::Foundation::Collections::CollectionChange::ItemRemoved, lastChange); + VERIFY_ARE_EQUAL(L"EventKey", lastKey); + + // Clear fires Reset + values.Insert(L"A", winrt::box_value(L"1")); + values.Insert(L"B", winrt::box_value(L"2")); + callCount = 0; + values.Clear(); + VERIFY_ARE_EQUAL(1, callCount); + VERIFY_ARE_EQUAL(winrt::Windows::Foundation::Collections::CollectionChange::Reset, lastChange); + + // Unregister event + observableMap.MapChanged(token); + values.Insert(L"NoEvent", winrt::box_value(L"NoEvent")); + VERIFY_ARE_EQUAL(1, callCount); // Should not have changed + values.Clear(); + } + + TEST_METHOD(Close_ThrowsAfterClose) + { + auto applicationData{ winrt::Microsoft::Windows::Storage::ApplicationData::GetForUnpackaged(L"TestApplicationData_Contoso", L"SupermarketPointOfSale") }; + auto localSettings{ applicationData.LocalSettings() }; + + // Create a child container to test Close on + auto child{ localSettings.CreateContainer(L"CloseTest", winrt::Microsoft::Windows::Storage::ApplicationDataCreateDisposition::Always) }; + VERIFY_IS_NOT_NULL(child); + VERIFY_ARE_EQUAL(L"CloseTest", child.Name()); + VERIFY_ARE_EQUAL(winrt::Microsoft::Windows::Storage::ApplicationDataLocality::Local, child.Locality()); + + child.Close(); + + // After Close, all member accesses should throw RO_E_CLOSED + try + { + [[maybe_unused]] auto name{ child.Name() }; + VERIFY_FAIL(L"Success is not expected"); + } + catch (winrt::hresult_error& e) + { + VERIFY_ARE_EQUAL(RO_E_CLOSED, e.code(), WEX::Common::String().Format(L"0x%X %s", e.code(), e.message().c_str())); + } + + try + { + [[maybe_unused]] auto locality{ child.Locality() }; + VERIFY_FAIL(L"Success is not expected"); + } + catch (winrt::hresult_error& e) + { + VERIFY_ARE_EQUAL(RO_E_CLOSED, e.code(), WEX::Common::String().Format(L"0x%X %s", e.code(), e.message().c_str())); + } + + try + { + [[maybe_unused]] auto vals{ child.Values() }; + VERIFY_FAIL(L"Success is not expected"); + } + catch (winrt::hresult_error& e) + { + VERIFY_ARE_EQUAL(RO_E_CLOSED, e.code(), WEX::Common::String().Format(L"0x%X %s", e.code(), e.message().c_str())); + } + + try + { + [[maybe_unused]] auto containers{ child.Containers() }; + VERIFY_FAIL(L"Success is not expected"); + } + catch (winrt::hresult_error& e) + { + VERIFY_ARE_EQUAL(RO_E_CLOSED, e.code(), WEX::Common::String().Format(L"0x%X %s", e.code(), e.message().c_str())); + } + + try + { + child.CreateContainer(L"Sub", winrt::Microsoft::Windows::Storage::ApplicationDataCreateDisposition::Always); + VERIFY_FAIL(L"Success is not expected"); + } + catch (winrt::hresult_error& e) + { + VERIFY_ARE_EQUAL(RO_E_CLOSED, e.code(), WEX::Common::String().Format(L"0x%X %s", e.code(), e.message().c_str())); + } + + try + { + child.DeleteContainer(L"Sub"); + VERIFY_FAIL(L"Success is not expected"); + } + catch (winrt::hresult_error& e) + { + VERIFY_ARE_EQUAL(RO_E_CLOSED, e.code(), WEX::Common::String().Format(L"0x%X %s", e.code(), e.message().c_str())); + } + + // Cleanup + localSettings.DeleteContainer(L"CloseTest"); + } }; } From 8ee07875f9d6ed428a1c91bb2b885fd1dfe18fbf Mon Sep 17 00:00:00 2001 From: Howard Kapustein Date: Sun, 8 Mar 2026 22:38:40 -0700 Subject: [PATCH 10/24] Fix build errors --- dev/ApplicationData/ApplicationData.vcxitems | 2 + .../ApplicationData.vcxitems.filters | 6 ++ .../ApplicationDataTelemetry.h | 4 +- .../UnpackagedApplicationData.cpp | 83 ++++++++----------- dev/Common/Common.vcxitems.filters | 5 +- 5 files changed, 46 insertions(+), 54 deletions(-) diff --git a/dev/ApplicationData/ApplicationData.vcxitems b/dev/ApplicationData/ApplicationData.vcxitems index 84c3681acc..294625ac54 100644 --- a/dev/ApplicationData/ApplicationData.vcxitems +++ b/dev/ApplicationData/ApplicationData.vcxitems @@ -17,6 +17,7 @@ + @@ -24,6 +25,7 @@ + diff --git a/dev/ApplicationData/ApplicationData.vcxitems.filters b/dev/ApplicationData/ApplicationData.vcxitems.filters index a220c13730..09888534a4 100644 --- a/dev/ApplicationData/ApplicationData.vcxitems.filters +++ b/dev/ApplicationData/ApplicationData.vcxitems.filters @@ -20,6 +20,9 @@ Source Files + + Source Files + @@ -37,6 +40,9 @@ Header Files + + Header Files + diff --git a/dev/ApplicationData/ApplicationDataTelemetry.h b/dev/ApplicationData/ApplicationDataTelemetry.h index 4dc8478805..3b00ac5566 100644 --- a/dev/ApplicationData/ApplicationDataTelemetry.h +++ b/dev/ApplicationData/ApplicationDataTelemetry.h @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft Corporation and Contributors. +// Copyright (c) Microsoft Corporation and Contributors. // Licensed under the MIT license. #pragma once @@ -32,7 +32,7 @@ class ApplicationDataTelemetry : public wil::TraceLoggingProvider TraceLoggingClassWriteStart( UnpackagedClearAsync, _GENERIC_PARTB_FIELDS_ENABLED, - TraceLoggingInt32(static_cast(locality), "Locality", + TraceLoggingInt32(static_cast(locality), "Locality"), TraceLoggingWideString(publisher.c_str(), "Publisher"), TraceLoggingWideString(product.c_str(), "Product")); } diff --git a/dev/ApplicationData/UnpackagedApplicationData.cpp b/dev/ApplicationData/UnpackagedApplicationData.cpp index 6663000edf..0f7dc2d12d 100644 --- a/dev/ApplicationData/UnpackagedApplicationData.cpp +++ b/dev/ApplicationData/UnpackagedApplicationData.cpp @@ -161,71 +161,58 @@ namespace Microsoft::Windows::Storage { _VerifyNotClosed(); - auto logTelemetry{ ApplicationDataTelemetry::UnpackagedClearAsync::Start(locality, m_publisher, m_product) }; + const winrt::hstring publisher{ m_publisher }; + const winrt::hstring product{ m_product }; + winrt::hstring localityPath; + switch (locality) + { + case winrt::Microsoft::Windows::Storage::ApplicationDataLocality::Local: + { + localityPath = LocalPath(); + break; + } + case winrt::Microsoft::Windows::Storage::ApplicationDataLocality::Temporary: + { + localityPath = TemporaryPath(); + break; + } + case winrt::Microsoft::Windows::Storage::ApplicationDataLocality::Machine: + { + localityPath = MachinePath(); + break; + } + default: + THROW_HR_MSG(E_INVALIDARG, "%d", static_cast(locality)); + } - auto strong{ get_strong() }; + auto logTelemetry{ ApplicationDataTelemetry::UnpackagedClearAsync::Start(locality, publisher, product) }; logTelemetry.IgnoreCurrentThread(); co_await winrt::resume_background(); auto logTelemetryContinuation{ logTelemetry.ContinueOnCurrentThread() }; - switch (locality) - { - case winrt::Microsoft::Windows::Storage::ApplicationDataLocality::Local: + // Clear the path + if (!localityPath.empty()) { - // Clear local path - const auto localPath{ LocalPath() }; - if (!localPath.empty()) + std::filesystem::path path{ localityPath.c_str() }; + if (_PathExists(path)) { - std::filesystem::path path{ localPath.c_str() }; - if (_PathExists(path)) - { - const auto options{ wil::RemoveDirectoryOptions::KeepRootDirectory | wil::RemoveDirectoryOptions::RemoveReadOnly }; - THROW_IF_FAILED(wil::RemoveDirectoryRecursiveNoThrow(path.c_str(), options)); - } + const auto options{ wil::RemoveDirectoryOptions::KeepRootDirectory | wil::RemoveDirectoryOptions::RemoveReadOnly }; + THROW_IF_FAILED(wil::RemoveDirectoryRecursiveNoThrow(path.c_str(), options)); } + } - // Clear local settings + // Clear the settings (if necessary) + if (locality == winrt::Microsoft::Windows::Storage::ApplicationDataLocality::Local) + { wil::unique_hkey currentUserKey; THROW_IF_WIN32_ERROR(::RegOpenCurrentUser(KEY_READ | KEY_WRITE, currentUserKey.put())); - auto subKey{ std::format(L"SOFTWARE\\{}\\{}", m_publisher, m_product) }; + auto subKey{ std::format(L"SOFTWARE\\{}\\{}", publisher, product) }; const auto hr{ HRESULT_FROM_WIN32(::RegDeleteTreeW(currentUserKey.get(), subKey.c_str())) }; if (hr != HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND) && hr != HRESULT_FROM_WIN32(ERROR_PATH_NOT_FOUND)) { THROW_IF_FAILED(hr); } - break; - } - case winrt::Microsoft::Windows::Storage::ApplicationDataLocality::Temporary: - { - const auto temporaryPath{ TemporaryPath() }; - if (!temporaryPath.empty()) - { - std::filesystem::path path{ temporaryPath.c_str() }; - if (_PathExists(path)) - { - const auto options{ wil::RemoveDirectoryOptions::KeepRootDirectory | wil::RemoveDirectoryOptions::RemoveReadOnly }; - THROW_IF_FAILED(wil::RemoveDirectoryRecursiveNoThrow(path.c_str(), options)); - } - } - break; - } - case winrt::Microsoft::Windows::Storage::ApplicationDataLocality::Machine: - { - const auto machinePath{ MachinePath() }; - if (!machinePath.empty()) - { - std::filesystem::path path{ machinePath.c_str() }; - if (_PathExists(path)) - { - const auto options{ wil::RemoveDirectoryOptions::KeepRootDirectory | wil::RemoveDirectoryOptions::RemoveReadOnly }; - THROW_IF_FAILED(wil::RemoveDirectoryRecursiveNoThrow(path.c_str(), options)); - } - } - break; - } - default: - THROW_HR_MSG(E_INVALIDARG, "%d", static_cast(locality)); } logTelemetry.Stop(); diff --git a/dev/Common/Common.vcxitems.filters b/dev/Common/Common.vcxitems.filters index 0fec4421a8..3dabc04cff 100644 --- a/dev/Common/Common.vcxitems.filters +++ b/dev/Common/Common.vcxitems.filters @@ -50,9 +50,6 @@ Header Files - - Header Files - Header Files @@ -77,4 +74,4 @@ Source Files - + \ No newline at end of file From 905360e24437f90176c0f91a47f7b9fe334ea499 Mon Sep 17 00:00:00 2001 From: Howard Kapustein Date: Sun, 8 Mar 2026 22:55:52 -0700 Subject: [PATCH 11/24] Fix build errors --- test/ApplicationData/UnpackagedApplicationDataTests.cpp | 2 +- test/ApplicationData/pch.h | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/test/ApplicationData/UnpackagedApplicationDataTests.cpp b/test/ApplicationData/UnpackagedApplicationDataTests.cpp index 68c3862675..a04312d69e 100644 --- a/test/ApplicationData/UnpackagedApplicationDataTests.cpp +++ b/test/ApplicationData/UnpackagedApplicationDataTests.cpp @@ -676,7 +676,7 @@ namespace Test::ApplicationData::Tests auto values{ localSettings.Values() }; values.Clear(); - namespace PV = winrt::Windows::Foundation::PropertyValue; + using PV = winrt::Windows::Foundation::PropertyValue; namespace WF = winrt::Windows::Foundation; // UInt8Array diff --git a/test/ApplicationData/pch.h b/test/ApplicationData/pch.h index 81002f7b7c..2837841ab5 100644 --- a/test/ApplicationData/pch.h +++ b/test/ApplicationData/pch.h @@ -11,6 +11,7 @@ #include #include +#include #include #include From 7ff4abfbdafaf0e13cd3e8f57f4fe734fc2c02ee Mon Sep 17 00:00:00 2001 From: Howard Kapustein Date: Sun, 8 Mar 2026 23:20:15 -0700 Subject: [PATCH 12/24] Fix build errors --- .../UnpackagedApplicationDataTests.cpp | 25 +++++++++++++++---- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/test/ApplicationData/UnpackagedApplicationDataTests.cpp b/test/ApplicationData/UnpackagedApplicationDataTests.cpp index a04312d69e..bbdeaa6577 100644 --- a/test/ApplicationData/UnpackagedApplicationDataTests.cpp +++ b/test/ApplicationData/UnpackagedApplicationDataTests.cpp @@ -608,8 +608,8 @@ namespace Test::ApplicationData::Tests VERIFY_ARE_EQUAL(2.718281828, winrt::unbox_value(values.Lookup(L"Double"))); // Char16 - values.Insert(L"Char16", winrt::box_value(L'Z')); - VERIFY_ARE_EQUAL(L'Z', winrt::unbox_value(values.Lookup(L"Char16"))); + values.Insert(L"Char16", winrt::box_value(u'Z')); + VERIFY_ARE_EQUAL(u'Z', winrt::unbox_value(values.Lookup(L"Char16"))); // Boolean true values.Insert(L"BoolTrue", winrt::box_value(true)); @@ -681,7 +681,12 @@ namespace Test::ApplicationData::Tests // UInt8Array { - winrt::com_array arr{ 1, 2, 3, 255 }; + // An initializer list like: { 1, 2, 3, 255 } is always an initializer_list unless told otherwise. + // winrt::com_array has a templated constructor that accepts iterators / initializer lists, so the compiler happily + // tries to copy int values into T, and then quite correctly warns you that: “I am truncating integers into smaller + // integer types. I hope you meant that.” Better to make the initializer list elements match the array element type. + std::array arrValues{ 1, 2, 3, 255 }; + winrt::com_array arr(arrValues.begin(), arrValues.end()); values.Insert(L"UInt8Array", PV::CreateUInt8Array(arr)); auto pv{ values.Lookup(L"UInt8Array").as() }; winrt::com_array result; @@ -693,7 +698,12 @@ namespace Test::ApplicationData::Tests // Int16Array { - winrt::com_array arr{ -32768, 0, 32767 }; + // An initializer list like: { 1, 2, 3, 255 } is always an initializer_list unless told otherwise. + // winrt::com_array has a templated constructor that accepts iterators / initializer lists, so the compiler happily + // tries to copy int values into T, and then quite correctly warns you that: “I am truncating integers into smaller + // integer types. I hope you meant that.” Better to make the initializer list elements match the array element type. + std::array arrValues{ -32768, 0, 32767 }; + winrt::com_array arr(arrValues.begin(), arrValues.end()); values.Insert(L"Int16Array", PV::CreateInt16Array(arr)); auto pv{ values.Lookup(L"Int16Array").as() }; winrt::com_array result; @@ -705,7 +715,12 @@ namespace Test::ApplicationData::Tests // UInt16Array { - winrt::com_array arr{ 0, 1000, 65535 }; + // An initializer list like: { 1, 2, 3, 255 } is always an initializer_list unless told otherwise. + // winrt::com_array has a templated constructor that accepts iterators / initializer lists, so the compiler happily + // tries to copy int values into T, and then quite correctly warns you that: “I am truncating integers into smaller + // integer types. I hope you meant that.” Better to make the initializer list elements match the array element type. + std::array arrValues{ 0, 1000, 65535 }; + winrt::com_array arr(arrValues.begin(), arrValues.end()); values.Insert(L"UInt16Array", PV::CreateUInt16Array(arr)); auto pv{ values.Lookup(L"UInt16Array").as() }; winrt::com_array result; From 69bd47827aeab6ed9c0564bacdd0e6026e231361 Mon Sep 17 00:00:00 2001 From: Howard Kapustein Date: Mon, 9 Mar 2026 14:22:18 -0700 Subject: [PATCH 13/24] Fix test errors --- .../UnpackagedApplicationDataContainer.cpp | 36 +++++++++++++++++- .../UnpackagedApplicationDataContainer.h | 3 ++ .../UnpackagedApplicationDataTests.cpp | 37 +++++++++++++------ 3 files changed, 62 insertions(+), 14 deletions(-) diff --git a/dev/ApplicationData/UnpackagedApplicationDataContainer.cpp b/dev/ApplicationData/UnpackagedApplicationDataContainer.cpp index 42b3c6874c..060ca23788 100644 --- a/dev/ApplicationData/UnpackagedApplicationDataContainer.cpp +++ b/dev/ApplicationData/UnpackagedApplicationDataContainer.cpp @@ -686,6 +686,9 @@ namespace Microsoft::Windows::Storage void Clear() { + // Best-effort but on failure report the first + HRESULT hrFirst{}; + // Collect all value names first, then delete std::vector names; for (const auto& valueData : wil::make_range(wil::reg::value_iterator{ m_key.get() }, wil::reg::value_iterator{})) @@ -694,10 +697,22 @@ namespace Microsoft::Windows::Storage } for (const auto& name : names) { - ::RegDeleteValueW(m_key.get(), name.c_str()); + const HRESULT hrDeleteValue{ HRESULT_FROM_WIN32(::RegDeleteValueW(m_key.get(), name.c_str())) }; + if (FAILED(hrDeleteValue) && !wil::reg::is_registry_not_found(hrDeleteValue)) + { + std::ignore = LOG_HR_MSG(hrDeleteValue, "%ls", name.c_str()); + if (SUCCEEDED(hrFirst)) + { + hrFirst = hrDeleteValue; + } + } } m_mapChanged(*this, winrt::make( - winrt::Windows::Foundation::Collections::CollectionChange::Reset, winrt::hstring{})); + FAILED(hrFirst) ? + winrt::Windows::Foundation::Collections::CollectionChange::ItemRemoved : + winrt::Windows::Foundation::Collections::CollectionChange::Reset, + winrt::hstring{})); + THROW_IF_FAILED(hrFirst); } // IIterable @@ -797,6 +812,8 @@ namespace Microsoft::Windows::Storage winrt::Windows::Foundation::Collections::IMap UnpackagedApplicationDataContainer::Containers() { + _VerifyNotClosed(); + auto map{ winrt::single_threaded_map() }; for (const auto& keyData : wil::make_range(wil::reg::key_iterator{ m_key.get() }, wil::reg::key_iterator{})) { @@ -810,16 +827,22 @@ namespace Microsoft::Windows::Storage winrt::hstring UnpackagedApplicationDataContainer::Name() { + _VerifyNotClosed(); + return m_name; } winrt::Microsoft::Windows::Storage::ApplicationDataLocality UnpackagedApplicationDataContainer::Locality() { + _VerifyNotClosed(); + return m_locality; } winrt::Windows::Foundation::Collections::IPropertySet UnpackagedApplicationDataContainer::Values() { + _VerifyNotClosed(); + return winrt::make(m_key); } @@ -832,6 +855,8 @@ namespace Microsoft::Windows::Storage winrt::hstring const& name, winrt::Microsoft::Windows::Storage::ApplicationDataCreateDisposition const& disposition) { + _VerifyNotClosed(); + if (disposition == winrt::Microsoft::Windows::Storage::ApplicationDataCreateDisposition::Existing) { auto subKey{ wil::reg::open_shared_key(m_key.get(), name.c_str(), wil::reg::key_access::readwrite) }; @@ -848,10 +873,17 @@ namespace Microsoft::Windows::Storage void UnpackagedApplicationDataContainer::DeleteContainer(winrt::hstring const& name) { + _VerifyNotClosed(); + const auto hr{ HRESULT_FROM_WIN32(::RegDeleteTreeW(m_key.get(), name.c_str())) }; if ((hr != HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND)) && (hr != HRESULT_FROM_WIN32(ERROR_PATH_NOT_FOUND))) { THROW_IF_WIN32_ERROR_MSG(::RegDeleteTreeW(m_key.get(), name.c_str()), "%ls", name.c_str()); } } + + void UnpackagedApplicationDataContainer::_VerifyNotClosed() + { + THROW_HR_IF_NULL(RO_E_CLOSED, m_key); + } } diff --git a/dev/ApplicationData/UnpackagedApplicationDataContainer.h b/dev/ApplicationData/UnpackagedApplicationDataContainer.h index 3512507a51..46bf78a38e 100644 --- a/dev/ApplicationData/UnpackagedApplicationDataContainer.h +++ b/dev/ApplicationData/UnpackagedApplicationDataContainer.h @@ -20,6 +20,9 @@ namespace Microsoft::Windows::Storage winrt::Microsoft::Windows::Storage::ApplicationDataContainer CreateContainer(winrt::hstring const& name, winrt::Microsoft::Windows::Storage::ApplicationDataCreateDisposition const& disposition); void DeleteContainer(winrt::hstring const& name); + private: + void _VerifyNotClosed(); + private: wil::shared_hkey m_key; winrt::hstring m_name; diff --git a/test/ApplicationData/UnpackagedApplicationDataTests.cpp b/test/ApplicationData/UnpackagedApplicationDataTests.cpp index bbdeaa6577..62c47d65bc 100644 --- a/test/ApplicationData/UnpackagedApplicationDataTests.cpp +++ b/test/ApplicationData/UnpackagedApplicationDataTests.cpp @@ -14,6 +14,8 @@ static const winrt::hstring null_hstring; namespace Test::ApplicationData::Tests { + const auto Main_PackageFamilyName{ ::TP::DynamicDependencyDataStore::c_PackageFamilyName }; + const winrt::hstring Publisher{ L"ApplicationDataTests" }; const winrt::hstring Product{ L"UnpackagedApplicationDataTests" }; @@ -103,6 +105,17 @@ namespace Test::ApplicationData::Tests } } + std::filesystem::path ToLongPath(const winrt::hstring& path) + { + DWORD size{ GetLongPathNameW(path.c_str(), nullptr, 0) }; + VERIFY_ARE_NOT_EQUAL(0u, size); + std::wstring longPath(size, L'\0'); + size = GetLongPathNameW(path.c_str(), longPath.data(), size); + VERIFY_ARE_NOT_EQUAL(0u, size); + longPath.resize(size); // Trim the trailing NUL that Windows kindly included + return std::filesystem::path{ longPath.c_str() }; + } + TEST_METHOD(GetForUnpackaged_InvalidParameter) { const winrt::hstring publisher{ L"FabrikamTest" }; @@ -233,8 +246,9 @@ namespace Test::ApplicationData::Tests VERIFY_IS_NOT_NULL(applicationData); const auto localFolder{ applicationData.LocalFolder() }; - const auto localPath{ applicationData.LocalPath() }; - VERIFY_ARE_EQUAL(localFolder.Path(), localPath); + const auto localFolderPath{ ToLongPath(localFolder.Path()) }; + const auto localPath{ ToLongPath(applicationData.LocalPath()) }; + VERIFY_ARE_EQUAL(localFolderPath, localPath); const auto expectedLocalFolder{ UserLocalFolder(Publisher, Product) }; const auto expectedLocalPath{ UserLocalPath(Publisher, Product) }; VERIFY_ARE_EQUAL(localPath, expectedLocalPath); @@ -272,8 +286,9 @@ namespace Test::ApplicationData::Tests VERIFY_IS_NOT_NULL(applicationData); const auto temporaryFolder{ applicationData.TemporaryFolder() }; - const auto temporaryPath{ applicationData.TemporaryPath() }; - VERIFY_ARE_EQUAL(temporaryFolder.Path(), temporaryPath); + const auto temporaryFolderPath{ ToLongPath(temporaryFolder.Path()) }; + const auto temporaryPath{ ToLongPath(applicationData.TemporaryPath()) }; + VERIFY_ARE_EQUAL(temporaryFolderPath, temporaryPath); const auto expectedTemporaryFolder{ UserTemporaryFolder(Publisher, Product) }; const auto expectedTemporaryPath{ UserTemporaryPath(Publisher, Product) }; VERIFY_ARE_EQUAL(temporaryPath, expectedTemporaryPath); @@ -321,7 +336,6 @@ namespace Test::ApplicationData::Tests TEST_METHOD(LocalSettings) { -#ifdef TODO_LocalSettings winrt::hstring packageFamilyName{ Main_PackageFamilyName }; auto applicationData{ winrt::Microsoft::Windows::Storage::ApplicationData::GetForUnpackaged(Publisher, Product) }; VERIFY_IS_NOT_NULL(applicationData); @@ -459,7 +473,6 @@ namespace Test::ApplicationData::Tests { VERIFY_ARE_EQUAL(RO_E_CLOSED, e.code(), WEX::Common::String().Format(L"0x%X %s", e.code(), e.message().c_str())); } -#endif } TEST_METHOD(ClearAsync) @@ -683,8 +696,8 @@ namespace Test::ApplicationData::Tests { // An initializer list like: { 1, 2, 3, 255 } is always an initializer_list unless told otherwise. // winrt::com_array has a templated constructor that accepts iterators / initializer lists, so the compiler happily - // tries to copy int values into T, and then quite correctly warns you that: “I am truncating integers into smaller - // integer types. I hope you meant that.” Better to make the initializer list elements match the array element type. + // tries to copy int values into T, and then quite correctly warns you that: “I am truncating integers into smaller + // integer types. I hope you meant that.†Better to make the initializer list elements match the array element type. std::array arrValues{ 1, 2, 3, 255 }; winrt::com_array arr(arrValues.begin(), arrValues.end()); values.Insert(L"UInt8Array", PV::CreateUInt8Array(arr)); @@ -700,8 +713,8 @@ namespace Test::ApplicationData::Tests { // An initializer list like: { 1, 2, 3, 255 } is always an initializer_list unless told otherwise. // winrt::com_array has a templated constructor that accepts iterators / initializer lists, so the compiler happily - // tries to copy int values into T, and then quite correctly warns you that: “I am truncating integers into smaller - // integer types. I hope you meant that.” Better to make the initializer list elements match the array element type. + // tries to copy int values into T, and then quite correctly warns you that: “I am truncating integers into smaller + // integer types. I hope you meant that.†Better to make the initializer list elements match the array element type. std::array arrValues{ -32768, 0, 32767 }; winrt::com_array arr(arrValues.begin(), arrValues.end()); values.Insert(L"Int16Array", PV::CreateInt16Array(arr)); @@ -717,8 +730,8 @@ namespace Test::ApplicationData::Tests { // An initializer list like: { 1, 2, 3, 255 } is always an initializer_list unless told otherwise. // winrt::com_array has a templated constructor that accepts iterators / initializer lists, so the compiler happily - // tries to copy int values into T, and then quite correctly warns you that: “I am truncating integers into smaller - // integer types. I hope you meant that.” Better to make the initializer list elements match the array element type. + // tries to copy int values into T, and then quite correctly warns you that: “I am truncating integers into smaller + // integer types. I hope you meant that.†Better to make the initializer list elements match the array element type. std::array arrValues{ 0, 1000, 65535 }; winrt::com_array arr(arrValues.begin(), arrValues.end()); values.Insert(L"UInt16Array", PV::CreateUInt16Array(arr)); From 371a6571faa0a0e50ce12635e29c8c4f3ed67306 Mon Sep 17 00:00:00 2001 From: Howard Kapustein Date: Mon, 9 Mar 2026 18:44:28 -0700 Subject: [PATCH 14/24] More test fixes --- .../UnpackagedApplicationDataContainer.cpp | 15 +++- test/ApplicationData/ApplicationDataTests.cpp | 20 ++--- .../UnpackagedApplicationDataTests.cpp | 90 +++++-------------- test/inc/WindowsAppRuntime.Test.FileSystem.h | 73 +++++++++++++++ .../WindowsAppRuntime.Test.TAEF.cppwinrt.h | 54 ++++++++++- 5 files changed, 170 insertions(+), 82 deletions(-) diff --git a/dev/ApplicationData/UnpackagedApplicationDataContainer.cpp b/dev/ApplicationData/UnpackagedApplicationDataContainer.cpp index 060ca23788..81f4423b55 100644 --- a/dev/ApplicationData/UnpackagedApplicationDataContainer.cpp +++ b/dev/ApplicationData/UnpackagedApplicationDataContainer.cpp @@ -645,8 +645,19 @@ namespace Microsoft::Windows::Storage bool HasKey(winrt::hstring const& key) const { DWORD type{}; - const auto status{ ::RegQueryValueExW(m_key.get(), key.c_str(), nullptr, &type, nullptr, nullptr) }; - return status == ERROR_SUCCESS; + const auto hr{ HRESULT_FROM_WIN32(::RegQueryValueExW(m_key.get(), key.c_str(), nullptr, &type, nullptr, nullptr)) }; + if (SUCCEEDED(hr)) + { + return true; + } + else if (wil::reg::is_registry_not_found(hr)) + { + return false; + } + else + { + THROW_HR_MSG(hr, "%ls", key.c_str()); + } } winrt::Windows::Foundation::Collections::IMapView GetView() const diff --git a/test/ApplicationData/ApplicationDataTests.cpp b/test/ApplicationData/ApplicationDataTests.cpp index e48649be5e..f38309d1c3 100644 --- a/test/ApplicationData/ApplicationDataTests.cpp +++ b/test/ApplicationData/ApplicationDataTests.cpp @@ -49,12 +49,12 @@ namespace Test::ApplicationData::Tests TEST_METHOD(GetDefault_Main) { - //TODO + //TODO GetDefault_Main } TEST_METHOD(GetDefault_Framework) { - //TODO + //TODO GetDefault_Framework } TEST_METHOD(GetForPackageFamily_Main) @@ -86,12 +86,12 @@ namespace Test::ApplicationData::Tests TEST_METHOD(GetForUser_Main) { - //TODO + //TODO GetForUser_Main } TEST_METHOD(GetForUser_Framework) { - //TODO + //TODO GetForUser_Framework } TEST_METHOD(FolderAndPath_Main) @@ -426,32 +426,32 @@ namespace Test::ApplicationData::Tests TEST_METHOD(ClearAsync_Main) { - //TODO + //TODO ClearAsync_Main } TEST_METHOD(ClearAsync_Framework) { - //TODO + //TODO ClearAsync_Framework } TEST_METHOD(ClearFolderAsync_Machine_Main) { - //TODO + //TODO ClearFolderAsync_Machine_Main } TEST_METHOD(ClearFolderAsync_Machine_Framework) { - //TODO + //TODO ClearFolderAsync_Machine_Framework } TEST_METHOD(ClearPublisherCacheFolderAsync_Main) { - //TODO + //TODO ClearPublisherCacheFolderAsync_Main } TEST_METHOD(ClearPublisherCacheFolderAsync_Framework) { - //TODO + //TODO ClearPublisherCacheFolderAsync_Framework } }; } diff --git a/test/ApplicationData/UnpackagedApplicationDataTests.cpp b/test/ApplicationData/UnpackagedApplicationDataTests.cpp index 62c47d65bc..e7d4ad6cb1 100644 --- a/test/ApplicationData/UnpackagedApplicationDataTests.cpp +++ b/test/ApplicationData/UnpackagedApplicationDataTests.cpp @@ -288,10 +288,10 @@ namespace Test::ApplicationData::Tests const auto temporaryFolder{ applicationData.TemporaryFolder() }; const auto temporaryFolderPath{ ToLongPath(temporaryFolder.Path()) }; const auto temporaryPath{ ToLongPath(applicationData.TemporaryPath()) }; - VERIFY_ARE_EQUAL(temporaryFolderPath, temporaryPath); + VERIFY_ARE_EQUAL(temporaryFolderPath, temporaryPath, WEX::Common::String().Format(L"Expected:%ls Actual:%ls", temporaryFolderPath.c_str(), temporaryPath.c_str())); const auto expectedTemporaryFolder{ UserTemporaryFolder(Publisher, Product) }; const auto expectedTemporaryPath{ UserTemporaryPath(Publisher, Product) }; - VERIFY_ARE_EQUAL(temporaryPath, expectedTemporaryPath); + VERIFY_ARE_EQUAL(temporaryPath, expectedTemporaryPath, WEX::Common::String().Format(L"Expected:%ls Actual:%ls", expectedTemporaryPath.c_str(), temporaryPath.c_str())); } TEST_METHOD(PublisherCacheFolderAndPath) @@ -340,22 +340,14 @@ namespace Test::ApplicationData::Tests auto applicationData{ winrt::Microsoft::Windows::Storage::ApplicationData::GetForUnpackaged(Publisher, Product) }; VERIFY_IS_NOT_NULL(applicationData); - auto systemApplicationData{ winrt::Windows::Management::Core::ApplicationDataManager::CreateForPackageFamily(packageFamilyName) }; - VERIFY_IS_NOT_NULL(systemApplicationData); - const auto localSettings{ applicationData.LocalSettings() }; - const auto systemLocalSettings{ systemApplicationData.LocalSettings() }; - VERIFY_ARE_EQUAL(static_cast(localSettings.Locality()), static_cast(systemLocalSettings.Locality())); + VERIFY_ARE_EQUAL(winrt::Microsoft::Windows::Storage::ApplicationDataLocality::Local, localSettings.Locality()); auto containers{ localSettings.Containers() }; VERIFY_ARE_EQUAL(0u, containers.Size()); - auto systemContainers{ systemLocalSettings.Containers() }; - VERIFY_ARE_EQUAL(0u, systemContainers.Size()); - VERIFY_ARE_EQUAL(containers.Size(), systemContainers.Size()); const winrt::hstring foodAndStuff{ L"FoodAndStuff" }; VERIFY_IS_FALSE(containers.HasKey(foodAndStuff)); - VERIFY_IS_FALSE(systemContainers.HasKey(foodAndStuff)); auto container{ localSettings.CreateContainer(foodAndStuff, winrt::Microsoft::Windows::Storage::ApplicationDataCreateDisposition::Always) }; VERIFY_ARE_EQUAL(foodAndStuff, container.Name()); @@ -368,15 +360,6 @@ namespace Test::ApplicationData::Tests container = containers.Lookup(foodAndStuff); VERIFY_IS_NOT_NULL(container); VERIFY_ARE_EQUAL(foodAndStuff, container.Name()); - // - VERIFY_ARE_EQUAL(0u, systemContainers.Size()); - VERIFY_IS_FALSE(systemContainers.HasKey(foodAndStuff)); - systemContainers = systemLocalSettings.Containers(); - VERIFY_ARE_EQUAL(1u, systemContainers.Size()); - VERIFY_IS_TRUE(systemContainers.HasKey(foodAndStuff)); - auto systemContainer{ systemContainers.Lookup(foodAndStuff) }; - VERIFY_IS_NOT_NULL(systemContainer); - VERIFY_ARE_EQUAL(foodAndStuff, systemContainer.Name()); const winrt::hstring keyMeat{ L"Meat" }; const winrt::hstring rawValueSteak{ L"Steak" }; @@ -391,29 +374,12 @@ namespace Test::ApplicationData::Tests VERIFY_IS_NOT_NULL(steakLookupAsReferenceString); auto steakString{ steakLookupAsReferenceString.GetString() }; VERIFY_ARE_EQUAL(rawValueSteak, steakString); - // - auto systemValues{ systemContainer.Values() }; - VERIFY_ARE_EQUAL(1u, systemValues.Size()); - auto systemSteak{ systemValues.Lookup(keyMeat) }; - VERIFY_IS_NOT_NULL(systemSteak); - auto systemSteakLookupAsReferenceString{ systemSteak.try_as>() }; - VERIFY_IS_NOT_NULL(systemSteakLookupAsReferenceString); - auto systemSteakString{ systemSteakLookupAsReferenceString.GetString() }; - VERIFY_ARE_EQUAL(rawValueSteak, systemSteakString); const winrt::hstring keyDrink{ L"Drink" }; const winrt::hstring rawValueWhiskey{ L"Whiskey" }; auto valueWhiskey{ winrt::Windows::Foundation::PropertyValue::CreateString(rawValueWhiskey) }; - VERIFY_ARE_EQUAL(1u, systemValues.Size()); - systemValues.Insert(keyDrink, valueWhiskey); - VERIFY_ARE_EQUAL(2u, systemValues.Size()); - auto systemWhiskey{ systemValues.Lookup(keyDrink) }; - VERIFY_IS_NOT_NULL(systemWhiskey); - auto systemWhiskeyLookupAsReferenceString{ systemWhiskey.try_as>() }; - VERIFY_IS_NOT_NULL(systemWhiskeyLookupAsReferenceString); - auto systemWhiskeyString{ systemWhiskeyLookupAsReferenceString.GetString() }; - VERIFY_ARE_EQUAL(rawValueWhiskey, systemWhiskeyString); - // + VERIFY_ARE_EQUAL(1u, values.Size()); + values.Insert(keyDrink, valueWhiskey); VERIFY_ARE_EQUAL(2u, values.Size()); auto whiskey{ values.Lookup(keyDrink) }; VERIFY_IS_NOT_NULL(whiskey); @@ -433,23 +399,10 @@ namespace Test::ApplicationData::Tests { VERIFY_ARE_EQUAL(RO_E_CLOSED, e.code(), WEX::Common::String().Format(L"0x%X %s", e.code(), e.message().c_str())); } - VERIFY_ARE_EQUAL(winrt::Windows::Storage::ApplicationDataLocality::Local, systemContainer.Locality()); - systemContainer.Close(); - try - { - [[maybe_unused]] auto locality{ systemContainer.Locality() }; - VERIFY_FAIL(L"Success is not expected"); - } - catch (winrt::hresult_error& e) - { - VERIFY_ARE_EQUAL(RO_E_CLOSED, e.code(), WEX::Common::String().Format(L"0x%X %s", e.code(), e.message().c_str())); - } VERIFY_ARE_EQUAL(1u, localSettings.Containers().Size()); - VERIFY_ARE_EQUAL(1u, systemLocalSettings.Containers().Size()); localSettings.DeleteContainer(foodAndStuff); VERIFY_ARE_EQUAL(0u, localSettings.Containers().Size()); - VERIFY_ARE_EQUAL(0u, systemLocalSettings.Containers().Size()); VERIFY_ARE_EQUAL(winrt::Microsoft::Windows::Storage::ApplicationDataLocality::Local, localSettings.Locality()); localSettings.Close(); @@ -462,27 +415,26 @@ namespace Test::ApplicationData::Tests { VERIFY_ARE_EQUAL(RO_E_CLOSED, e.code(), WEX::Common::String().Format(L"0x%X %s", e.code(), e.message().c_str())); } - VERIFY_ARE_EQUAL(winrt::Windows::Storage::ApplicationDataLocality::Local, systemLocalSettings.Locality()); - systemLocalSettings.Close(); - try - { - [[maybe_unused]] auto locality{ systemLocalSettings.Locality() }; - VERIFY_FAIL(L"Success is not expected"); - } - catch (winrt::hresult_error& e) - { - VERIFY_ARE_EQUAL(RO_E_CLOSED, e.code(), WEX::Common::String().Format(L"0x%X %s", e.code(), e.message().c_str())); - } } - TEST_METHOD(ClearAsync) + TEST_METHOD(ClearAsync_LocalPath) + { + //TODO ClearAsync_LocalPath + } + + TEST_METHOD(ClearAsync_LocalSettings) + { + //TODO ClearAsync_LocalSettings + } + + TEST_METHOD(ClearAsync_Local) { - //TODO + //TODO ClearAsync_Local } - TEST_METHOD(ClearFolderAsync_Machine) + TEST_METHOD(ClearAsync_MachinePath) { - //TODO + //TODO ClearAsync_MachinePath } TEST_METHOD(ClearPublisherCacheFolderAsync) @@ -715,7 +667,7 @@ namespace Test::ApplicationData::Tests // winrt::com_array has a templated constructor that accepts iterators / initializer lists, so the compiler happily // tries to copy int values into T, and then quite correctly warns you that: “I am truncating integers into smaller // integer types. I hope you meant that.†Better to make the initializer list elements match the array element type. - std::array arrValues{ -32768, 0, 32767 }; + std::array arrValues{ -32768, 0, 32767 }; winrt::com_array arr(arrValues.begin(), arrValues.end()); values.Insert(L"Int16Array", PV::CreateInt16Array(arr)); auto pv{ values.Lookup(L"Int16Array").as() }; @@ -732,7 +684,7 @@ namespace Test::ApplicationData::Tests // winrt::com_array has a templated constructor that accepts iterators / initializer lists, so the compiler happily // tries to copy int values into T, and then quite correctly warns you that: “I am truncating integers into smaller // integer types. I hope you meant that.†Better to make the initializer list elements match the array element type. - std::array arrValues{ 0, 1000, 65535 }; + std::array arrValues{ 0, 1000, 65535 }; winrt::com_array arr(arrValues.begin(), arrValues.end()); values.Insert(L"UInt16Array", PV::CreateUInt16Array(arr)); auto pv{ values.Lookup(L"UInt16Array").as() }; diff --git a/test/inc/WindowsAppRuntime.Test.FileSystem.h b/test/inc/WindowsAppRuntime.Test.FileSystem.h index b8b5235942..e07f08ff77 100644 --- a/test/inc/WindowsAppRuntime.Test.FileSystem.h +++ b/test/inc/WindowsAppRuntime.Test.FileSystem.h @@ -86,4 +86,77 @@ namespace Test::FileSystem } } +namespace WEX::TestExecution +{ + // Teach TAEF how to format a std::filesystem::path + template <> + class VerifyOutputTraits + { + public: + static WEX::Common::NoThrowString ToString(std::filesystem::path const& value) + { + const auto s{ value.c_str() }; + if (!s) + { + return WEX::Common::NoThrowString(L"nullptr"); + } + else + { + return WEX::Common::NoThrowString().Format(L"\"%s\"", s); + } + } + }; + + // Teach TAEF how to compare a std::filesystem::path + template <> + class VerifyCompareTraits + { + public: + static bool AreEqual(std::filesystem::path const& expected, std::filesystem::path const& actual) + { + return Compare(expected, actual) == 0; + } + + static bool AreSame(std::filesystem::path const& expected, std::filesystem::path const& actual) + { + return &expected == &actual; + } + + static bool IsLessThan(std::filesystem::path const& expectedLess, std::filesystem::path const& expectedGreater) + { + return Compare(expectedLess, expectedGreater) < 0; + } + + static bool IsGreaterThan(std::filesystem::path const& expectedGreater, std::filesystem::path const& expectedLess) + { + return Compare(expectedGreater, expectedLess) > 0; + } + + static bool IsNull(std::filesystem::path const& object) + { + return object.c_str() == nullptr; + } + private: + static int Compare(std::filesystem::path const& left, std::filesystem::path const& right) + { + if (left == right) + { + return 0; + } + else if (left.c_str() == nullptr) + { + return -1; + } + else if (right.c_str() == nullptr) + { + return 1; + } + else + { + return CompareStringOrdinal(left .c_str(), -1, right.c_str(), -1, FALSE) - CSTR_EQUAL; + } + } + }; +} + #endif // __WINDOWSAPPRUNTIME_TEST_FILESYSTEM_H diff --git a/test/inc/WindowsAppRuntime.Test.TAEF.cppwinrt.h b/test/inc/WindowsAppRuntime.Test.TAEF.cppwinrt.h index 2662a1eb27..f9f9732799 100644 --- a/test/inc/WindowsAppRuntime.Test.TAEF.cppwinrt.h +++ b/test/inc/WindowsAppRuntime.Test.TAEF.cppwinrt.h @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft Corporation and Contributors. All rights reserved. +// Copyright (c) Microsoft Corporation and Contributors. All rights reserved. // Licensed under the MIT License. #ifndef __WINDOWSAPPRUNTIME_TEST_TAEF_CPPWINRT_H @@ -77,4 +77,56 @@ namespace WEX::TestExecution }; } +namespace winrt +{ +/// Define a winrt::hstring variant that compares as case-independent (L"ABC" == L"abc" == L"aBc") +class hstring_nocase +{ +public: + explicit hstring_nocase(winrt::hstring const& string) : m_string(string) { } + + explicit hstring_nocase(PCWSTR string) : m_string(string) { } + + ~hstring_nocase() = default; + + bool empty() const { return m_string.empty(); } + + PCWSTR c_str() const { return m_string.c_str(); } + +public: + static int compare(winrt::hstring_nocase const& left, winrt::hstring_nocase const& right) + { + return CompareStringOrdinal(left.c_str(), -1, right.c_str(), -1, TRUE) - CSTR_EQUAL; + } + +private: + winrt::hstring m_string; +}; +} + +bool operator==(winrt::hstring_nocase const& left, winrt::hstring_nocase const& right) +{ + return winrt::hstring_nocase::compare(left, right) == 0; +} +bool operator!=(winrt::hstring_nocase const& left, winrt::hstring_nocase const& right) +{ + return winrt::hstring_nocase::compare(left, right) != 0; +} +bool operator<(winrt::hstring_nocase const& left, winrt::hstring_nocase const& right) +{ + return winrt::hstring_nocase::compare(left, right) < 0; +} +bool operator<=(winrt::hstring_nocase const& left, winrt::hstring_nocase const& right) +{ + return winrt::hstring_nocase::compare(left, right) <= 0; +} +bool operator>(winrt::hstring_nocase const& left, winrt::hstring_nocase const& right) +{ + return winrt::hstring_nocase::compare(left, right) > 0; +} +bool operator>=(winrt::hstring_nocase const& left, winrt::hstring_nocase const& right) +{ + return winrt::hstring_nocase::compare(left, right) >= 0; +} + #endif // __WINDOWSAPPRUNTIME_TEST_TAEF_CPPWINRT_H From 2f7c5b7141907874c6c9b870b483fc68f33cafbf Mon Sep 17 00:00:00 2001 From: Howard Kapustein Date: Mon, 9 Mar 2026 20:26:51 -0700 Subject: [PATCH 15/24] Test fix. DeleteContainer fix --- .../UnpackagedApplicationDataContainer.cpp | 8 ++++++-- test/ApplicationData/UnpackagedApplicationDataTests.cpp | 4 ++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/dev/ApplicationData/UnpackagedApplicationDataContainer.cpp b/dev/ApplicationData/UnpackagedApplicationDataContainer.cpp index 81f4423b55..e023d20442 100644 --- a/dev/ApplicationData/UnpackagedApplicationDataContainer.cpp +++ b/dev/ApplicationData/UnpackagedApplicationDataContainer.cpp @@ -887,9 +887,13 @@ namespace Microsoft::Windows::Storage _VerifyNotClosed(); const auto hr{ HRESULT_FROM_WIN32(::RegDeleteTreeW(m_key.get(), name.c_str())) }; - if ((hr != HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND)) && (hr != HRESULT_FROM_WIN32(ERROR_PATH_NOT_FOUND))) + if (SUCCEEDED(hr)) { - THROW_IF_WIN32_ERROR_MSG(::RegDeleteTreeW(m_key.get(), name.c_str()), "%ls", name.c_str()); + return; + } + else if (!wil::reg::is_registry_not_found(hr)) + { + THROW_HR_MSG(hr, "%ls", name.c_str()); } } diff --git a/test/ApplicationData/UnpackagedApplicationDataTests.cpp b/test/ApplicationData/UnpackagedApplicationDataTests.cpp index e7d4ad6cb1..56c875e2f1 100644 --- a/test/ApplicationData/UnpackagedApplicationDataTests.cpp +++ b/test/ApplicationData/UnpackagedApplicationDataTests.cpp @@ -250,7 +250,7 @@ namespace Test::ApplicationData::Tests const auto localPath{ ToLongPath(applicationData.LocalPath()) }; VERIFY_ARE_EQUAL(localFolderPath, localPath); const auto expectedLocalFolder{ UserLocalFolder(Publisher, Product) }; - const auto expectedLocalPath{ UserLocalPath(Publisher, Product) }; + const auto expectedLocalPath{ ToLongPath(UserLocalPath(Publisher, Product).c_str()) }; VERIFY_ARE_EQUAL(localPath, expectedLocalPath); } @@ -290,7 +290,7 @@ namespace Test::ApplicationData::Tests const auto temporaryPath{ ToLongPath(applicationData.TemporaryPath()) }; VERIFY_ARE_EQUAL(temporaryFolderPath, temporaryPath, WEX::Common::String().Format(L"Expected:%ls Actual:%ls", temporaryFolderPath.c_str(), temporaryPath.c_str())); const auto expectedTemporaryFolder{ UserTemporaryFolder(Publisher, Product) }; - const auto expectedTemporaryPath{ UserTemporaryPath(Publisher, Product) }; + const auto expectedTemporaryPath{ ToLongPath(UserTemporaryPath(Publisher, Product)) }; VERIFY_ARE_EQUAL(temporaryPath, expectedTemporaryPath, WEX::Common::String().Format(L"Expected:%ls Actual:%ls", expectedTemporaryPath.c_str(), temporaryPath.c_str())); } From f3412fd93efd6cd49495542809f5a98790853e58 Mon Sep 17 00:00:00 2001 From: Howard Kapustein Date: Tue, 10 Mar 2026 16:51:07 -0700 Subject: [PATCH 16/24] Fixed an inverted check --- dev/ApplicationData/UnpackagedApplicationDataContainer.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dev/ApplicationData/UnpackagedApplicationDataContainer.cpp b/dev/ApplicationData/UnpackagedApplicationDataContainer.cpp index e023d20442..50c88b2309 100644 --- a/dev/ApplicationData/UnpackagedApplicationDataContainer.cpp +++ b/dev/ApplicationData/UnpackagedApplicationDataContainer.cpp @@ -685,7 +685,7 @@ namespace Microsoft::Windows::Storage m_mapChanged(*this, winrt::make( existed ? winrt::Windows::Foundation::Collections::CollectionChange::ItemChanged : winrt::Windows::Foundation::Collections::CollectionChange::ItemInserted, key)); - return !existed; + return existed; } void Remove(winrt::hstring const& key) From 9ae80b12be504234e4c48d5cc967058c3f0624b3 Mon Sep 17 00:00:00 2001 From: Howard Kapustein Date: Tue, 10 Mar 2026 16:58:00 -0700 Subject: [PATCH 17/24] Fix tests --- test/ApplicationData/ApplicationDataTests.cpp | 1 - test/ApplicationData/ApplicationDataTests_Elevated.cpp | 5 ++++- test/ApplicationData/UnpackagedApplicationDataTests.cpp | 1 - 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/test/ApplicationData/ApplicationDataTests.cpp b/test/ApplicationData/ApplicationDataTests.cpp index f38309d1c3..1682a58a72 100644 --- a/test/ApplicationData/ApplicationDataTests.cpp +++ b/test/ApplicationData/ApplicationDataTests.cpp @@ -22,7 +22,6 @@ namespace Test::ApplicationData::Tests public: BEGIN_TEST_CLASS(ApplicationDataTests) TEST_CLASS_PROPERTY(L"ThreadingModel", L"MTA") - TEST_CLASS_PROPERTY(L"IsolationLevel", L"Class") TEST_CLASS_PROPERTY(L"RunAs", L"RestrictedUser") END_TEST_CLASS() diff --git a/test/ApplicationData/ApplicationDataTests_Elevated.cpp b/test/ApplicationData/ApplicationDataTests_Elevated.cpp index 5e69cc4db1..a15e533a39 100644 --- a/test/ApplicationData/ApplicationDataTests_Elevated.cpp +++ b/test/ApplicationData/ApplicationDataTests_Elevated.cpp @@ -34,7 +34,6 @@ namespace Test::ApplicationData::Tests public: BEGIN_TEST_CLASS(ApplicationDataTests_Elevated) TEST_CLASS_PROPERTY(L"ThreadingModel", L"MTA") - TEST_CLASS_PROPERTY(L"IsolationLevel", L"Class") TEST_CLASS_PROPERTY(L"RunAs", L"ElevatedUser") TEST_CLASS_PROPERTY(L"RunFixtureAs", L"System") END_TEST_CLASS() @@ -117,6 +116,9 @@ namespace Test::ApplicationData::Tests TEST_METHOD(MachineFolderAndPath_Framework_Supported) { +#if !defined(TODO_MACHINEFOLDER_PATH_DISAPPEARING_FOR_FRAMEWORK_AFTER_SETUP_BEFORE_TEST) + WEX::Logging::Log::Result(WEX::Logging::TestResults::Skipped, L"ApplicationDataTests_Elevated::MachineFolderAndPath_Framework_Supported: MachineFolder created in Setup is missing when TestMethod starts. Skipping test"); +#else ::TB::Setup(); winrt::hstring packageFamilyName{ Framework_PackageFamilyName }; @@ -134,6 +136,7 @@ namespace Test::ApplicationData::Tests VERIFY_ARE_EQUAL(machinePath, winrt::hstring(expectedMachinePath.c_str())); ::TB::Cleanup(); +#endif // !defined(TODO_MACHINEFOLDER_PATH_DISAPPEARING_FOR_FRAMEWORK_AFTER_SETUP_BEFORE_TEST) } }; } diff --git a/test/ApplicationData/UnpackagedApplicationDataTests.cpp b/test/ApplicationData/UnpackagedApplicationDataTests.cpp index 56c875e2f1..cd9915f8b6 100644 --- a/test/ApplicationData/UnpackagedApplicationDataTests.cpp +++ b/test/ApplicationData/UnpackagedApplicationDataTests.cpp @@ -24,7 +24,6 @@ namespace Test::ApplicationData::Tests public: BEGIN_TEST_CLASS(UnpackagedApplicationDataTests) TEST_CLASS_PROPERTY(L"ThreadingModel", L"MTA") - TEST_CLASS_PROPERTY(L"IsolationLevel", L"Class") TEST_CLASS_PROPERTY(L"RunAs", L"RestrictedUser") END_TEST_CLASS() From 54620d13bb6deef457efa5150eff42ea58a43b18 Mon Sep 17 00:00:00 2001 From: Howard Kapustein Date: Wed, 25 Mar 2026 23:07:02 -0700 Subject: [PATCH 18/24] Incorporated feeedback. In progress --- dev/ApplicationData/ApplicationData.vcxitems | 1 + .../ApplicationData.vcxitems.filters | 3 + dev/ApplicationData/M.W.S.ApplicationData.cpp | 13 ++- dev/ApplicationData/M.W.S.ApplicationData.h | 4 + .../UnpackagedApplicationData.cpp | 6 +- .../UnpackagedApplicationData.h | 1 - .../UnpackagedApplicationDataContainer.cpp | 23 +++++ .../UnpackagedApplicationDataContainer.h | 1 + dev/ApplicationData/Validate.h | 99 +++++++++++++++++++ dev/ApplicationData/pch.h | 1 + .../UnpackagedApplicationDataTests.cpp | 38 +++++++ 11 files changed, 183 insertions(+), 7 deletions(-) create mode 100644 dev/ApplicationData/Validate.h diff --git a/dev/ApplicationData/ApplicationData.vcxitems b/dev/ApplicationData/ApplicationData.vcxitems index 294625ac54..a25bda3ce0 100644 --- a/dev/ApplicationData/ApplicationData.vcxitems +++ b/dev/ApplicationData/ApplicationData.vcxitems @@ -26,6 +26,7 @@ + diff --git a/dev/ApplicationData/ApplicationData.vcxitems.filters b/dev/ApplicationData/ApplicationData.vcxitems.filters index 09888534a4..b0a21278ad 100644 --- a/dev/ApplicationData/ApplicationData.vcxitems.filters +++ b/dev/ApplicationData/ApplicationData.vcxitems.filters @@ -43,6 +43,9 @@ Header Files + + Header Files + diff --git a/dev/ApplicationData/M.W.S.ApplicationData.cpp b/dev/ApplicationData/M.W.S.ApplicationData.cpp index 234846deee..6144963f39 100644 --- a/dev/ApplicationData/M.W.S.ApplicationData.cpp +++ b/dev/ApplicationData/M.W.S.ApplicationData.cpp @@ -109,8 +109,9 @@ namespace winrt::Microsoft::Windows::Storage::implementation } winrt::Microsoft::Windows::Storage::ApplicationData ApplicationData::GetForUnpackaged(hstring const& publisher, hstring const& product) { - THROW_HR_IF_MSG(E_INVALIDARG, publisher.empty(), "Publisher not valid"); - THROW_HR_IF_MSG(E_INVALIDARG, product.empty(), "Product not valid"); + _VerifyPublisher(publisher); + _VerifyProduct(publisher); + return winrt::make(publisher, product); } bool ApplicationData::IsMachinePathSupported() @@ -412,4 +413,12 @@ namespace winrt::Microsoft::Windows::Storage::implementation } return path; } + bool ApplicationData::_VerifyPublisher(PCWSTR string) + { + return !::Microsoft::Foundation::String::IsNullOrEmpty(string) && !is_prohibited_string(string); + } + bool ApplicationData::_VerifyProduct(PCWSTR string) + { + return !::Microsoft::Foundation::String::IsNullOrEmpty(string) && !is_prohibited_string(string); + } } diff --git a/dev/ApplicationData/M.W.S.ApplicationData.h b/dev/ApplicationData/M.W.S.ApplicationData.h index 8047cb550e..59aee846b8 100644 --- a/dev/ApplicationData/M.W.S.ApplicationData.h +++ b/dev/ApplicationData/M.W.S.ApplicationData.h @@ -43,6 +43,10 @@ namespace winrt::Microsoft::Windows::Storage::implementation static std::filesystem::path _MachinePath(hstring const& packageFamilyName); static bool _PathExists(std::filesystem::path const& path); static hstring StorageFolderToPath(winrt::Windows::Storage::StorageFolder storageFolder); + static bool string_contains_any(winrt::hstring const& string, _In_ PCWSTR characters); + static bool string_contains_any(PCWSTR string, _In_ PCWSTR characters); + static bool _VerifyPublisher(PCWSTR string); + static bool _VerifyProduct(PCWSTR string); private: std::unique_ptr<::Microsoft::Windows::Storage::UnpackagedApplicationData> m_unpackagedApplicationData; diff --git a/dev/ApplicationData/UnpackagedApplicationData.cpp b/dev/ApplicationData/UnpackagedApplicationData.cpp index 0f7dc2d12d..b3ebbfef26 100644 --- a/dev/ApplicationData/UnpackagedApplicationData.cpp +++ b/dev/ApplicationData/UnpackagedApplicationData.cpp @@ -17,8 +17,7 @@ namespace Microsoft::Windows::Storage UnpackagedApplicationData::UnpackagedApplicationData(winrt::hstring const& publisher, winrt::hstring const& product) : m_publisher(publisher), m_product(product), - m_localPath(), - m_machinePath() + m_localPath() { } bool UnpackagedApplicationData::IsMachinePathSupported() @@ -182,7 +181,7 @@ namespace Microsoft::Windows::Storage break; } default: - THROW_HR_MSG(E_INVALIDARG, "%d", static_cast(locality)); + THROW_HR_MSG(E_NOTIMPL, "%d", static_cast(locality)); } auto logTelemetry{ ApplicationDataTelemetry::UnpackagedClearAsync::Start(locality, publisher, product) }; @@ -232,7 +231,6 @@ namespace Microsoft::Windows::Storage m_product.clear(); m_localPath.clear(); m_machinePath.clear(); - m_temporaryPath.clear(); } winrt::hstring UnpackagedApplicationData::GetPublisherCachePath(winrt::hstring const& folderName) { diff --git a/dev/ApplicationData/UnpackagedApplicationData.h b/dev/ApplicationData/UnpackagedApplicationData.h index 1b23c70e8e..4424cde99d 100644 --- a/dev/ApplicationData/UnpackagedApplicationData.h +++ b/dev/ApplicationData/UnpackagedApplicationData.h @@ -39,7 +39,6 @@ namespace Microsoft::Windows::Storage winrt::hstring m_publisher; winrt::hstring m_product; winrt::hstring m_localPath; - std::filesystem::path m_machinePath; winrt::hstring m_temporaryPath; }; } diff --git a/dev/ApplicationData/UnpackagedApplicationDataContainer.cpp b/dev/ApplicationData/UnpackagedApplicationDataContainer.cpp index 50c88b2309..8b88ed2ad9 100644 --- a/dev/ApplicationData/UnpackagedApplicationDataContainer.cpp +++ b/dev/ApplicationData/UnpackagedApplicationDataContainer.cpp @@ -126,12 +126,15 @@ namespace Microsoft::Windows::Storage { std::vector buffer(dataSize); THROW_IF_WIN32_ERROR(::RegQueryValueExW(key, valueName, nullptr, nullptr, buffer.data(), &dataSize)); + THROW_HR_IF(E_UNEXPECTED, dataSize % sizeof(wchar_t) != 0); auto* p{ reinterpret_cast(buffer.data()) }; auto* end{ reinterpret_cast(buffer.data() + dataSize) }; std::vector strings; while (p < end && *p != L'\0') { winrt::hstring str{ p }; + auto len{ wcsnlen(p, static_cast(end - p)) }; + winrt::hstring str{ p, static_cast(len) }; strings.push_back(str); p += str.size() + 1; } @@ -673,6 +676,7 @@ namespace Microsoft::Windows::Storage catch (...) { // Skip values that cannot be read + LOG_CAUGHT_EXCEPTION(); } } return snapshot.GetView(); @@ -741,6 +745,7 @@ namespace Microsoft::Windows::Storage catch (...) { // Skip values that cannot be read + LOG_CAUGHT_EXCEPTION(); } } return winrt::make(std::move(items)); @@ -868,6 +873,8 @@ namespace Microsoft::Windows::Storage { _VerifyNotClosed(); + _VerifyContainerName(name); + if (disposition == winrt::Microsoft::Windows::Storage::ApplicationDataCreateDisposition::Existing) { auto subKey{ wil::reg::open_shared_key(m_key.get(), name.c_str(), wil::reg::key_access::readwrite) }; @@ -886,6 +893,8 @@ namespace Microsoft::Windows::Storage { _VerifyNotClosed(); + _VerifyContainerName(name); + const auto hr{ HRESULT_FROM_WIN32(::RegDeleteTreeW(m_key.get(), name.c_str())) }; if (SUCCEEDED(hr)) { @@ -901,4 +910,18 @@ namespace Microsoft::Windows::Storage { THROW_HR_IF_NULL(RO_E_CLOSED, m_key); } + + void UnpackagedApplicationDataContainer::_VerifyContainerName(winrt::hstring const& name); + { + THROW_HR_IF_MSG(E_INVALIDARG, name.empty(), "Container name not valid (%ls)", name.c_str()); + + for (PCWSTR s = name.c_str(); *s != L'\0'; ++s) + { + THROW_HR_IF_MSG(E_INVALIDARG, *s == L'\\', "Container name not valid (%ls)", name.c_str()); + } + + THROW_HR_IF_MSG(E_INVALIDARG, contains_prohibited_characters(name), "Container name not valid (%ls)", name.c_str()); + THROW_HR_IF_MSG(E_INVALIDARG, CompareStringOrdinal(name.c_str(), -1, L".", -1, FALSE) == CSTR_EQUAL, "Container name not valid (%ls)", name.c_str()); + THROW_HR_IF_MSG(E_INVALIDARG, CompareStringOrdinal(name.c_str(), -1, L"..", -1, FALSE) == CSTR_EQUAL, "Container name not valid (%ls)", name.c_str()); + } } diff --git a/dev/ApplicationData/UnpackagedApplicationDataContainer.h b/dev/ApplicationData/UnpackagedApplicationDataContainer.h index 46bf78a38e..b6e660da9a 100644 --- a/dev/ApplicationData/UnpackagedApplicationDataContainer.h +++ b/dev/ApplicationData/UnpackagedApplicationDataContainer.h @@ -22,6 +22,7 @@ namespace Microsoft::Windows::Storage private: void _VerifyNotClosed(); + void _VerifyContainerName(winrt::hstring const& name); private: wil::shared_hkey m_key; diff --git a/dev/ApplicationData/Validate.h b/dev/ApplicationData/Validate.h new file mode 100644 index 0000000000..b59b34fb12 --- /dev/null +++ b/dev/ApplicationData/Validate.h @@ -0,0 +1,99 @@ +// Copyright (c) Microsoft Corporation and Contributors. +// Licensed under the MIT License. + +namespace winrt::Microsoft::Windows::Storage::implementation +{ +/// Allowed characters = ASCII alphanumeric, SPACE, DASH, DOT, UNDERSCORE +/// Avoid https://learn.microsoft.com/en-us/windows/win32/fileio/naming-a-file +constexpr bool is_valid_character(const wchar_t c) noexcept +{ + return (L'0' <= c && c <= L'9') || + (L'A' <= c && c <= L'Z') || + (L'a' <= c && c <= L'z') || + (L' ' == c || L'-' == c || L'.' == c || L'_' == c); +} + +constexpr PCWSTR c_prohibitedStringsEquals[24]{ + L".", L"..", L"con", L"prn", L"aux", L"nul", + L"com1", L"com2", L"com3", L"com4", L"com5", L"com6", L"com7", L"com8", L"com9", + L"lpt1", L"lpt2", L"lpt3", L"lpt4", L"lpt5", L"lpt6", L"lpt7", L"lpt8", L"lpt9" +}; + +constexpr PCWSTR c_prohibitedStringsStartsWith[23]{ + "con.", "prn.", "aux.", "nul.", + "com1.", "com2.", "com3.", "com4.", "com5.", "com6.", "com7.", "com8.", "com9.", + "lpt1.", "lpt2.", "lpt3.", "lpt4.", "lpt5.", "lpt6.", "lpt7.", "lpt8.", "lpt9.", + "xn--" +}; + +constexpr wchar_t ascii_tolower_ordinal(const wchar_t c) noexcept +{ + return (c >= L'A' && c <= L'Z') ? (c | 0x20) : c; +} + +constexpr bool startswith_ordinal_nocase(_In_ PCWSTR string, _In_ PCWSTR prefix) +{ + for (;;) + { + const wchar_t pc{ *prefix++ }; + if (pc == L'\0') + { + // prefix fully matched + return true; + } + + const wchar_t sc{ *string++ }; + if (sc == 0) + { + // string ended before prefix so cannot match + return false; + } + + if (ascii_tolower_ordinal(sc) != ascii_tolower_ordinal(pc)) + { + return false; + } + } +} + +constexpr bool contains_prohibited_character(_In_ PCWSTR string) +{ + for (PCWSTR s = string; *s != L'\0'; ++s) + { + if (!is_valid_character(*s)) + { + return true; + } + } + return false; +} + +constexpr bool is_prohibited_string(_In_ PCWSTR string) +{ + // Contains prohibited character(s) + if (contains_prohibed_characters(string)) + { + return false; + } + + // Equals a prohibited string + for (size_t index = 0; index < ARRAYSIZE(c_prohibitedStringsEquals); ++index) + { + if (CompareStringOrdinal(string, -1, c_prohibitedStringsEquals[index], -1, TRUE) == CSTR_EQUAL) + { + return false; + } + } + + // Starts with a prohibited string + for (size_t index = 0; index < ARRAYSIZE(c_prohibitedStringsStartsWith); ++index) + { + if (startswith_ordinal_nocase(string, c_prohibitedStringsStartsWith[index])) + { + return false; + } + } + + return true; +} +} diff --git a/dev/ApplicationData/pch.h b/dev/ApplicationData/pch.h index 28bd7e5eb7..cc0646340d 100644 --- a/dev/ApplicationData/pch.h +++ b/dev/ApplicationData/pch.h @@ -30,5 +30,6 @@ #include #include +#include #include #include diff --git a/test/ApplicationData/UnpackagedApplicationDataTests.cpp b/test/ApplicationData/UnpackagedApplicationDataTests.cpp index cd9915f8b6..492993269f 100644 --- a/test/ApplicationData/UnpackagedApplicationDataTests.cpp +++ b/test/ApplicationData/UnpackagedApplicationDataTests.cpp @@ -446,6 +446,44 @@ namespace Test::ApplicationData::Tests VERIFY_ARE_EQUAL(E_NOTIMPL, asyncAction.ErrorCode()); } + TEST_METHOD(GetForUnpackaged_InvalidParameter) + { + constexpr PCWSTR c_invalidIds[]{ + L"", + L"foo/bar", + L"foo\\bar", + L"foo@bar", + L".", + L"..", + L"lpt1", + L"lpt1.invalid", + }; + for (PCWSTR invalidId : c_invalidIds) + { + try + { + const winrt::hstring invalid{ invalidId }; + [[maybe_unused]] auto applicationData{ winrt::Microsoft::Windows::Storage::ApplicationData::GetForUnpackaged(invalid, Product) }; + VERIFY_FAIL(L"Success is not expected"); + } + catch (winrt::hresult_error& e) + { + VERIFY_ARE_EQUAL(E_INVALIDARG, e.code(), WEX::Common::String().Format(L"Invalid:%s => 0x%X %s", invalid.c_str(), e.code(), e.message().c_str())); + } + + try + { + const winrt::hstring invalid{}; + [[maybe_unused]] auto applicationData{ winrt::Microsoft::Windows::Storage::ApplicationData::GetForUnpackaged(Publisher, invalid) }; + VERIFY_FAIL(L"Success is not expected"); + } + catch (winrt::hresult_error& e) + { + VERIFY_ARE_EQUAL(E_INVALIDARG, e.code(), WEX::Common::String().Format(L"Invalid:%s => 0x%X %s", invalid.c_str(), e.code(), e.message().c_str())); + } + } + } + TEST_METHOD(ContainerOperations) { auto applicationData{ winrt::Microsoft::Windows::Storage::ApplicationData::GetForUnpackaged(L"TestApplicationData_Contoso", L"SupermarketPointOfSale") }; From 9af415dd39898ccdd1d04e1947ff6be74c16f2cf Mon Sep 17 00:00:00 2001 From: Howard Kapustein Date: Thu, 26 Mar 2026 00:07:05 -0700 Subject: [PATCH 19/24] Fixes --- dev/ApplicationData/ApplicationData.vcxitems | 2 +- dev/ApplicationData/M.W.S.ApplicationData.cpp | 8 +- dev/ApplicationData/M.W.S.ApplicationData.h | 4 +- .../UnpackagedApplicationData.cpp | 1 - .../UnpackagedApplicationDataContainer.cpp | 5 +- dev/ApplicationData/Validate.h | 26 +++--- dev/ApplicationData/pch.h | 2 + .../UnpackagedApplicationDataTests.cpp | 87 +++++++------------ 8 files changed, 55 insertions(+), 80 deletions(-) diff --git a/dev/ApplicationData/ApplicationData.vcxitems b/dev/ApplicationData/ApplicationData.vcxitems index a25bda3ce0..3655db8ddf 100644 --- a/dev/ApplicationData/ApplicationData.vcxitems +++ b/dev/ApplicationData/ApplicationData.vcxitems @@ -7,7 +7,7 @@ - %(AdditionalIncludeDirectories);$(MSBuildThisFileDirectory) + %(AdditionalIncludeDirectories);$(MSBuildThisFileDirectory);$(RepoRoot)\dev\common diff --git a/dev/ApplicationData/M.W.S.ApplicationData.cpp b/dev/ApplicationData/M.W.S.ApplicationData.cpp index 6144963f39..991d402c64 100644 --- a/dev/ApplicationData/M.W.S.ApplicationData.cpp +++ b/dev/ApplicationData/M.W.S.ApplicationData.cpp @@ -413,12 +413,12 @@ namespace winrt::Microsoft::Windows::Storage::implementation } return path; } - bool ApplicationData::_VerifyPublisher(PCWSTR string) + bool ApplicationData::_VerifyPublisher(winrt::hstring const& string) { - return !::Microsoft::Foundation::String::IsNullOrEmpty(string) && !is_prohibited_string(string); + return !::Microsoft::Foundation::String::IsNullOrEmpty(string.c_str()) && !is_prohibited_string(string.c_str()); } - bool ApplicationData::_VerifyProduct(PCWSTR string) + bool ApplicationData::_VerifyProduct(winrt::hstring const& string) { - return !::Microsoft::Foundation::String::IsNullOrEmpty(string) && !is_prohibited_string(string); + return !::Microsoft::Foundation::String::IsNullOrEmpty(string.c_str()) && !is_prohibited_string(string.c_str()); } } diff --git a/dev/ApplicationData/M.W.S.ApplicationData.h b/dev/ApplicationData/M.W.S.ApplicationData.h index 59aee846b8..0e90f13b38 100644 --- a/dev/ApplicationData/M.W.S.ApplicationData.h +++ b/dev/ApplicationData/M.W.S.ApplicationData.h @@ -45,8 +45,8 @@ namespace winrt::Microsoft::Windows::Storage::implementation static hstring StorageFolderToPath(winrt::Windows::Storage::StorageFolder storageFolder); static bool string_contains_any(winrt::hstring const& string, _In_ PCWSTR characters); static bool string_contains_any(PCWSTR string, _In_ PCWSTR characters); - static bool _VerifyPublisher(PCWSTR string); - static bool _VerifyProduct(PCWSTR string); + static bool _VerifyPublisher(winrt::hstring const& string); + static bool _VerifyProduct(winrt::hstring const& string); private: std::unique_ptr<::Microsoft::Windows::Storage::UnpackagedApplicationData> m_unpackagedApplicationData; diff --git a/dev/ApplicationData/UnpackagedApplicationData.cpp b/dev/ApplicationData/UnpackagedApplicationData.cpp index b3ebbfef26..19423c451e 100644 --- a/dev/ApplicationData/UnpackagedApplicationData.cpp +++ b/dev/ApplicationData/UnpackagedApplicationData.cpp @@ -230,7 +230,6 @@ namespace Microsoft::Windows::Storage m_publisher.clear(); m_product.clear(); m_localPath.clear(); - m_machinePath.clear(); } winrt::hstring UnpackagedApplicationData::GetPublisherCachePath(winrt::hstring const& folderName) { diff --git a/dev/ApplicationData/UnpackagedApplicationDataContainer.cpp b/dev/ApplicationData/UnpackagedApplicationDataContainer.cpp index 8b88ed2ad9..8d509d8dff 100644 --- a/dev/ApplicationData/UnpackagedApplicationDataContainer.cpp +++ b/dev/ApplicationData/UnpackagedApplicationDataContainer.cpp @@ -132,7 +132,6 @@ namespace Microsoft::Windows::Storage std::vector strings; while (p < end && *p != L'\0') { - winrt::hstring str{ p }; auto len{ wcsnlen(p, static_cast(end - p)) }; winrt::hstring str{ p, static_cast(len) }; strings.push_back(str); @@ -911,7 +910,7 @@ namespace Microsoft::Windows::Storage THROW_HR_IF_NULL(RO_E_CLOSED, m_key); } - void UnpackagedApplicationDataContainer::_VerifyContainerName(winrt::hstring const& name); + void UnpackagedApplicationDataContainer::_VerifyContainerName(winrt::hstring const& name) { THROW_HR_IF_MSG(E_INVALIDARG, name.empty(), "Container name not valid (%ls)", name.c_str()); @@ -920,7 +919,7 @@ namespace Microsoft::Windows::Storage THROW_HR_IF_MSG(E_INVALIDARG, *s == L'\\', "Container name not valid (%ls)", name.c_str()); } - THROW_HR_IF_MSG(E_INVALIDARG, contains_prohibited_characters(name), "Container name not valid (%ls)", name.c_str()); + THROW_HR_IF_MSG(E_INVALIDARG, contains_prohibited_character(name), "Container name not valid (%ls)", name.c_str()); THROW_HR_IF_MSG(E_INVALIDARG, CompareStringOrdinal(name.c_str(), -1, L".", -1, FALSE) == CSTR_EQUAL, "Container name not valid (%ls)", name.c_str()); THROW_HR_IF_MSG(E_INVALIDARG, CompareStringOrdinal(name.c_str(), -1, L"..", -1, FALSE) == CSTR_EQUAL, "Container name not valid (%ls)", name.c_str()); } diff --git a/dev/ApplicationData/Validate.h b/dev/ApplicationData/Validate.h index b59b34fb12..9312f80634 100644 --- a/dev/ApplicationData/Validate.h +++ b/dev/ApplicationData/Validate.h @@ -5,7 +5,7 @@ namespace winrt::Microsoft::Windows::Storage::implementation { /// Allowed characters = ASCII alphanumeric, SPACE, DASH, DOT, UNDERSCORE /// Avoid https://learn.microsoft.com/en-us/windows/win32/fileio/naming-a-file -constexpr bool is_valid_character(const wchar_t c) noexcept +inline bool is_valid_character(const wchar_t c) noexcept { return (L'0' <= c && c <= L'9') || (L'A' <= c && c <= L'Z') || @@ -13,25 +13,25 @@ constexpr bool is_valid_character(const wchar_t c) noexcept (L' ' == c || L'-' == c || L'.' == c || L'_' == c); } -constexpr PCWSTR c_prohibitedStringsEquals[24]{ +inline PCWSTR c_prohibitedStringsEquals[24]{ L".", L"..", L"con", L"prn", L"aux", L"nul", L"com1", L"com2", L"com3", L"com4", L"com5", L"com6", L"com7", L"com8", L"com9", L"lpt1", L"lpt2", L"lpt3", L"lpt4", L"lpt5", L"lpt6", L"lpt7", L"lpt8", L"lpt9" }; -constexpr PCWSTR c_prohibitedStringsStartsWith[23]{ - "con.", "prn.", "aux.", "nul.", - "com1.", "com2.", "com3.", "com4.", "com5.", "com6.", "com7.", "com8.", "com9.", - "lpt1.", "lpt2.", "lpt3.", "lpt4.", "lpt5.", "lpt6.", "lpt7.", "lpt8.", "lpt9.", - "xn--" +inline PCWSTR c_prohibitedStringsStartsWith[23]{ + L"con.", L"prn.", L"aux.", L"nul.", + L"com1.", L"com2.", L"com3.", L"com4.", L"com5.", L"com6.", L"com7.", L"com8.", L"com9.", + L"lpt1.", L"lpt2.", L"lpt3.", L"lpt4.", L"lpt5.", L"lpt6.", L"lpt7.", L"lpt8.", L"lpt9.", + L"xn--" }; -constexpr wchar_t ascii_tolower_ordinal(const wchar_t c) noexcept +inline wchar_t ascii_tolower_ordinal(const wchar_t c) noexcept { return (c >= L'A' && c <= L'Z') ? (c | 0x20) : c; } -constexpr bool startswith_ordinal_nocase(_In_ PCWSTR string, _In_ PCWSTR prefix) +inline bool startswith_ordinal_nocase(_In_ PCWSTR string, _In_ PCWSTR prefix) { for (;;) { @@ -56,7 +56,7 @@ constexpr bool startswith_ordinal_nocase(_In_ PCWSTR string, _In_ PCWSTR prefix) } } -constexpr bool contains_prohibited_character(_In_ PCWSTR string) +inline bool contains_prohibited_character(_In_ PCWSTR string) { for (PCWSTR s = string; *s != L'\0'; ++s) { @@ -68,10 +68,10 @@ constexpr bool contains_prohibited_character(_In_ PCWSTR string) return false; } -constexpr bool is_prohibited_string(_In_ PCWSTR string) +inline bool is_prohibited_string(_In_ PCWSTR string) { // Contains prohibited character(s) - if (contains_prohibed_characters(string)) + if (contains_prohibited_character(string)) { return false; } @@ -79,7 +79,7 @@ constexpr bool is_prohibited_string(_In_ PCWSTR string) // Equals a prohibited string for (size_t index = 0; index < ARRAYSIZE(c_prohibitedStringsEquals); ++index) { - if (CompareStringOrdinal(string, -1, c_prohibitedStringsEquals[index], -1, TRUE) == CSTR_EQUAL) + if (::CompareStringOrdinal(string, -1, c_prohibitedStringsEquals[index], -1, TRUE) == CSTR_EQUAL) { return false; } diff --git a/dev/ApplicationData/pch.h b/dev/ApplicationData/pch.h index cc0646340d..b9095c50cb 100644 --- a/dev/ApplicationData/pch.h +++ b/dev/ApplicationData/pch.h @@ -33,3 +33,5 @@ #include #include #include + +#include "Validate.h" diff --git a/test/ApplicationData/UnpackagedApplicationDataTests.cpp b/test/ApplicationData/UnpackagedApplicationDataTests.cpp index 492993269f..f8197f18a1 100644 --- a/test/ApplicationData/UnpackagedApplicationDataTests.cpp +++ b/test/ApplicationData/UnpackagedApplicationDataTests.cpp @@ -119,29 +119,41 @@ namespace Test::ApplicationData::Tests { const winrt::hstring publisher{ L"FabrikamTest" }; const winrt::hstring product{ L"ApplicationDataTest" }; - const winrt::hstring emptyString; - try - { - [[maybe_unused]] auto unpackagedApplicationData{ winrt::Microsoft::Windows::Storage::ApplicationData::GetForUnpackaged(emptyString, Product) }; - VERIFY_FAIL(L"Success is not expected"); - } - catch (winrt::hresult_error& e) + constexpr static PCWSTR c_invalidIds[]{ + L"", + L"foo/bar", + L"foo\\bar", + L"foo@bar", + L".", + L"..", + L"lpt1", + L"lpt1.invalid", + }; + for (PCWSTR invalidId : c_invalidIds) { - VERIFY_ARE_EQUAL(E_INVALIDARG, e.code(), WEX::Common::String().Format(L"0x%X %s", e.code(), e.message().c_str())); - } + const winrt::hstring invalid{ invalidId }; + try + { + [[maybe_unused]] auto applicationData{ winrt::Microsoft::Windows::Storage::ApplicationData::GetForUnpackaged(invalid, Product) }; + VERIFY_FAIL(L"Success is not expected"); + } + catch (winrt::hresult_error& e) + { + VERIFY_ARE_EQUAL(E_INVALIDARG, e.code(), WEX::Common::String().Format(L"Publisher:%s Product:%s => 0x%X %s", invalid.c_str(), Product.c_str(), e.code(), e.message().c_str())); + } - try - { - [[maybe_unused]] auto unpackagedApplicationData{ winrt::Microsoft::Windows::Storage::ApplicationData::GetForUnpackaged(Publisher, emptyString) }; - VERIFY_FAIL(L"Success is not expected"); - } - catch (winrt::hresult_error& e) - { - VERIFY_ARE_EQUAL(E_INVALIDARG, e.code(), WEX::Common::String().Format(L"0x%X %s", e.code(), e.message().c_str())); + try + { + [[maybe_unused]] auto applicationData{ winrt::Microsoft::Windows::Storage::ApplicationData::GetForUnpackaged(Publisher, invalid) }; + VERIFY_FAIL(L"Success is not expected"); + } + catch (winrt::hresult_error& e) + { + VERIFY_ARE_EQUAL(E_INVALIDARG, e.code(), WEX::Common::String().Format(L"Publisher:%s Product:%s => 0x%X %s", Publisher.c_str(), invalid.c_str(), e.code(), e.message().c_str())); + } } } - TEST_METHOD(GetForUnpackaged) { auto applicationData{ winrt::Microsoft::Windows::Storage::ApplicationData::GetForUnpackaged(Publisher, Product) }; @@ -442,48 +454,11 @@ namespace Test::ApplicationData::Tests VERIFY_IS_NOT_NULL(applicationData); winrt::hstring folderName{ L"Does.Not.Exist" }; [[maybe_unused]] auto asyncAction{ applicationData.ClearPublisherCacheFolderAsync(folderName) }; + asyncAction.get(); // ClearPublisherCacheFolderAsync is expected to fail with E_NOTIMPL, but it should do so by completing the async action with an error, not by throwing from the method itself VERIFY_ARE_EQUAL(winrt::Windows::Foundation::AsyncStatus::Error, asyncAction.Status()); VERIFY_ARE_EQUAL(E_NOTIMPL, asyncAction.ErrorCode()); } - TEST_METHOD(GetForUnpackaged_InvalidParameter) - { - constexpr PCWSTR c_invalidIds[]{ - L"", - L"foo/bar", - L"foo\\bar", - L"foo@bar", - L".", - L"..", - L"lpt1", - L"lpt1.invalid", - }; - for (PCWSTR invalidId : c_invalidIds) - { - try - { - const winrt::hstring invalid{ invalidId }; - [[maybe_unused]] auto applicationData{ winrt::Microsoft::Windows::Storage::ApplicationData::GetForUnpackaged(invalid, Product) }; - VERIFY_FAIL(L"Success is not expected"); - } - catch (winrt::hresult_error& e) - { - VERIFY_ARE_EQUAL(E_INVALIDARG, e.code(), WEX::Common::String().Format(L"Invalid:%s => 0x%X %s", invalid.c_str(), e.code(), e.message().c_str())); - } - - try - { - const winrt::hstring invalid{}; - [[maybe_unused]] auto applicationData{ winrt::Microsoft::Windows::Storage::ApplicationData::GetForUnpackaged(Publisher, invalid) }; - VERIFY_FAIL(L"Success is not expected"); - } - catch (winrt::hresult_error& e) - { - VERIFY_ARE_EQUAL(E_INVALIDARG, e.code(), WEX::Common::String().Format(L"Invalid:%s => 0x%X %s", invalid.c_str(), e.code(), e.message().c_str())); - } - } - } - TEST_METHOD(ContainerOperations) { auto applicationData{ winrt::Microsoft::Windows::Storage::ApplicationData::GetForUnpackaged(L"TestApplicationData_Contoso", L"SupermarketPointOfSale") }; From 9262de9d3690530f743237f8691367197534ae4e Mon Sep 17 00:00:00 2001 From: Howard Kapustein Date: Thu, 26 Mar 2026 11:00:53 -0700 Subject: [PATCH 20/24] Added header --- dev/WindowsAppRuntime_DLL/pch.h | 1 + 1 file changed, 1 insertion(+) diff --git a/dev/WindowsAppRuntime_DLL/pch.h b/dev/WindowsAppRuntime_DLL/pch.h index 1ed0b64bbc..292e9e026a 100644 --- a/dev/WindowsAppRuntime_DLL/pch.h +++ b/dev/WindowsAppRuntime_DLL/pch.h @@ -57,6 +57,7 @@ #include #include #include +#include #include #include #include From d925d5751c329aa862c33449c589d86c89826e79 Mon Sep 17 00:00:00 2001 From: Howard Kapustein Date: Thu, 26 Mar 2026 18:54:19 -0700 Subject: [PATCH 21/24] Fixed --- dev/ApplicationData/M.W.S.ApplicationData.cpp | 14 ++++++++----- dev/ApplicationData/M.W.S.ApplicationData.h | 6 ++---- .../UnpackagedApplicationDataContainer.cpp | 4 +++- dev/ApplicationData/Validate.h | 10 +++++----- dev/WindowsAppRuntime_DLL/pch.h | 2 +- .../UnpackagedApplicationDataTests.cpp | 20 ++++++++++++------- 6 files changed, 33 insertions(+), 23 deletions(-) diff --git a/dev/ApplicationData/M.W.S.ApplicationData.cpp b/dev/ApplicationData/M.W.S.ApplicationData.cpp index 991d402c64..3fd36a45d3 100644 --- a/dev/ApplicationData/M.W.S.ApplicationData.cpp +++ b/dev/ApplicationData/M.W.S.ApplicationData.cpp @@ -14,6 +14,8 @@ #include "ApplicationDataTelemetry.h" +#include "Validate.h" + static_assert(static_cast(winrt::Microsoft::Windows::Storage::ApplicationDataLocality::Local) == static_cast(winrt::Windows::Storage::ApplicationDataLocality::Local)); static_assert(static_cast(winrt::Microsoft::Windows::Storage::ApplicationDataLocality::LocalCache) == static_cast(winrt::Windows::Storage::ApplicationDataLocality::LocalCache)); static_assert(static_cast(winrt::Microsoft::Windows::Storage::ApplicationDataLocality::SharedLocal) == static_cast(winrt::Windows::Storage::ApplicationDataLocality::SharedLocal)); @@ -110,7 +112,7 @@ namespace winrt::Microsoft::Windows::Storage::implementation winrt::Microsoft::Windows::Storage::ApplicationData ApplicationData::GetForUnpackaged(hstring const& publisher, hstring const& product) { _VerifyPublisher(publisher); - _VerifyProduct(publisher); + _VerifyProduct(product); return winrt::make(publisher, product); } @@ -413,12 +415,14 @@ namespace winrt::Microsoft::Windows::Storage::implementation } return path; } - bool ApplicationData::_VerifyPublisher(winrt::hstring const& string) + void ApplicationData::_VerifyPublisher(winrt::hstring const& string) { - return !::Microsoft::Foundation::String::IsNullOrEmpty(string.c_str()) && !is_prohibited_string(string.c_str()); + THROW_HR_IF_MSG(E_INVALIDARG, ::Microsoft::Foundation::String::IsNullOrEmpty(string.c_str()), "Publisher not valid (%ls)", string.c_str()); + THROW_HR_IF_MSG(E_INVALIDARG, ::Microsoft::Windows::Storage::is_prohibited_string(string.c_str()), "Publisher not valid (%ls)", string.c_str()); } - bool ApplicationData::_VerifyProduct(winrt::hstring const& string) + void ApplicationData::_VerifyProduct(winrt::hstring const& string) { - return !::Microsoft::Foundation::String::IsNullOrEmpty(string.c_str()) && !is_prohibited_string(string.c_str()); + THROW_HR_IF_MSG(E_INVALIDARG, ::Microsoft::Foundation::String::IsNullOrEmpty(string.c_str()), "Product not valid (%ls)", string.c_str()); + THROW_HR_IF_MSG(E_INVALIDARG, ::Microsoft::Windows::Storage::is_prohibited_string(string.c_str()), "Product not valid (%ls)", string.c_str()); } } diff --git a/dev/ApplicationData/M.W.S.ApplicationData.h b/dev/ApplicationData/M.W.S.ApplicationData.h index 0e90f13b38..11bb311a84 100644 --- a/dev/ApplicationData/M.W.S.ApplicationData.h +++ b/dev/ApplicationData/M.W.S.ApplicationData.h @@ -43,10 +43,8 @@ namespace winrt::Microsoft::Windows::Storage::implementation static std::filesystem::path _MachinePath(hstring const& packageFamilyName); static bool _PathExists(std::filesystem::path const& path); static hstring StorageFolderToPath(winrt::Windows::Storage::StorageFolder storageFolder); - static bool string_contains_any(winrt::hstring const& string, _In_ PCWSTR characters); - static bool string_contains_any(PCWSTR string, _In_ PCWSTR characters); - static bool _VerifyPublisher(winrt::hstring const& string); - static bool _VerifyProduct(winrt::hstring const& string); + static void _VerifyPublisher(winrt::hstring const& string); + static void _VerifyProduct(winrt::hstring const& string); private: std::unique_ptr<::Microsoft::Windows::Storage::UnpackagedApplicationData> m_unpackagedApplicationData; diff --git a/dev/ApplicationData/UnpackagedApplicationDataContainer.cpp b/dev/ApplicationData/UnpackagedApplicationDataContainer.cpp index 8d509d8dff..5bd863bc7d 100644 --- a/dev/ApplicationData/UnpackagedApplicationDataContainer.cpp +++ b/dev/ApplicationData/UnpackagedApplicationDataContainer.cpp @@ -5,6 +5,8 @@ #include "UnpackagedApplicationDataContainer.h" +#include "Validate.h" + namespace Microsoft::Windows::Storage { namespace @@ -919,7 +921,7 @@ namespace Microsoft::Windows::Storage THROW_HR_IF_MSG(E_INVALIDARG, *s == L'\\', "Container name not valid (%ls)", name.c_str()); } - THROW_HR_IF_MSG(E_INVALIDARG, contains_prohibited_character(name), "Container name not valid (%ls)", name.c_str()); + THROW_HR_IF_MSG(E_INVALIDARG, ::Microsoft::Windows::Storage::contains_prohibited_character(name.c_str()), "Container name not valid (%ls)", name.c_str()); THROW_HR_IF_MSG(E_INVALIDARG, CompareStringOrdinal(name.c_str(), -1, L".", -1, FALSE) == CSTR_EQUAL, "Container name not valid (%ls)", name.c_str()); THROW_HR_IF_MSG(E_INVALIDARG, CompareStringOrdinal(name.c_str(), -1, L"..", -1, FALSE) == CSTR_EQUAL, "Container name not valid (%ls)", name.c_str()); } diff --git a/dev/ApplicationData/Validate.h b/dev/ApplicationData/Validate.h index 9312f80634..cc80fb01e0 100644 --- a/dev/ApplicationData/Validate.h +++ b/dev/ApplicationData/Validate.h @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation and Contributors. // Licensed under the MIT License. -namespace winrt::Microsoft::Windows::Storage::implementation +namespace Microsoft::Windows::Storage { /// Allowed characters = ASCII alphanumeric, SPACE, DASH, DOT, UNDERSCORE /// Avoid https://learn.microsoft.com/en-us/windows/win32/fileio/naming-a-file @@ -73,7 +73,7 @@ inline bool is_prohibited_string(_In_ PCWSTR string) // Contains prohibited character(s) if (contains_prohibited_character(string)) { - return false; + return true; } // Equals a prohibited string @@ -81,7 +81,7 @@ inline bool is_prohibited_string(_In_ PCWSTR string) { if (::CompareStringOrdinal(string, -1, c_prohibitedStringsEquals[index], -1, TRUE) == CSTR_EQUAL) { - return false; + return true; } } @@ -90,10 +90,10 @@ inline bool is_prohibited_string(_In_ PCWSTR string) { if (startswith_ordinal_nocase(string, c_prohibitedStringsStartsWith[index])) { - return false; + return true; } } - return true; + return false; } } diff --git a/dev/WindowsAppRuntime_DLL/pch.h b/dev/WindowsAppRuntime_DLL/pch.h index 292e9e026a..f1ec626873 100644 --- a/dev/WindowsAppRuntime_DLL/pch.h +++ b/dev/WindowsAppRuntime_DLL/pch.h @@ -57,7 +57,7 @@ #include #include #include -#include +#include #include #include #include diff --git a/test/ApplicationData/UnpackagedApplicationDataTests.cpp b/test/ApplicationData/UnpackagedApplicationDataTests.cpp index f8197f18a1..3b14f683fb 100644 --- a/test/ApplicationData/UnpackagedApplicationDataTests.cpp +++ b/test/ApplicationData/UnpackagedApplicationDataTests.cpp @@ -136,7 +136,7 @@ namespace Test::ApplicationData::Tests try { [[maybe_unused]] auto applicationData{ winrt::Microsoft::Windows::Storage::ApplicationData::GetForUnpackaged(invalid, Product) }; - VERIFY_FAIL(L"Success is not expected"); + VERIFY_FAIL(WEX::Common::String().Format(L"Success is not expected -- Publisher:%s Product:%s", invalid.c_str(), Product.c_str())); } catch (winrt::hresult_error& e) { @@ -146,7 +146,7 @@ namespace Test::ApplicationData::Tests try { [[maybe_unused]] auto applicationData{ winrt::Microsoft::Windows::Storage::ApplicationData::GetForUnpackaged(Publisher, invalid) }; - VERIFY_FAIL(L"Success is not expected"); + VERIFY_FAIL(WEX::Common::String().Format(L"Success is not expected -- Publisher:%s Product:%s", Publisher.c_str(), invalid.c_str())); } catch (winrt::hresult_error& e) { @@ -452,11 +452,17 @@ namespace Test::ApplicationData::Tests { auto applicationData{ winrt::Microsoft::Windows::Storage::ApplicationData::GetForUnpackaged(Publisher, Product) }; VERIFY_IS_NOT_NULL(applicationData); - winrt::hstring folderName{ L"Does.Not.Exist" }; - [[maybe_unused]] auto asyncAction{ applicationData.ClearPublisherCacheFolderAsync(folderName) }; - asyncAction.get(); // ClearPublisherCacheFolderAsync is expected to fail with E_NOTIMPL, but it should do so by completing the async action with an error, not by throwing from the method itself - VERIFY_ARE_EQUAL(winrt::Windows::Foundation::AsyncStatus::Error, asyncAction.Status()); - VERIFY_ARE_EQUAL(E_NOTIMPL, asyncAction.ErrorCode()); + + try + { + winrt::hstring folderName{ L"Does.Not.Exist" }; + [[maybe_unused]] auto folder{ applicationData.GetPublisherCacheFolder(folderName) }; + VERIFY_FAIL(L"Success is not expected"); + } + catch (winrt::hresult_error& e) + { + VERIFY_ARE_EQUAL(E_NOTIMPL, e.code(), WEX::Common::String().Format(L"0x%X %s", e.code(), e.message().c_str())); + } } TEST_METHOD(ContainerOperations) From 2d3c380471afb2fcfe86d104081e3da21f17907c Mon Sep 17 00:00:00 2001 From: Howard Kapustein Date: Fri, 27 Mar 2026 16:05:17 -0700 Subject: [PATCH 22/24] Incorporated feedback --- .../UnpackagedApplicationData.cpp | 4 +- .../UnpackagedApplicationDataContainer.cpp | 7 +- .../UnpackagedApplicationDataTests.cpp | 113 ++++++++++++++++-- 3 files changed, 110 insertions(+), 14 deletions(-) diff --git a/dev/ApplicationData/UnpackagedApplicationData.cpp b/dev/ApplicationData/UnpackagedApplicationData.cpp index 19423c451e..e1d3ba000f 100644 --- a/dev/ApplicationData/UnpackagedApplicationData.cpp +++ b/dev/ApplicationData/UnpackagedApplicationData.cpp @@ -17,7 +17,8 @@ namespace Microsoft::Windows::Storage UnpackagedApplicationData::UnpackagedApplicationData(winrt::hstring const& publisher, winrt::hstring const& product) : m_publisher(publisher), m_product(product), - m_localPath() + m_localPath(), + m_temporaryPath() { } bool UnpackagedApplicationData::IsMachinePathSupported() @@ -230,6 +231,7 @@ namespace Microsoft::Windows::Storage m_publisher.clear(); m_product.clear(); m_localPath.clear(); + m_temporaryPath.clear(); } winrt::hstring UnpackagedApplicationData::GetPublisherCachePath(winrt::hstring const& folderName) { diff --git a/dev/ApplicationData/UnpackagedApplicationDataContainer.cpp b/dev/ApplicationData/UnpackagedApplicationDataContainer.cpp index 5bd863bc7d..9acdebcdeb 100644 --- a/dev/ApplicationData/UnpackagedApplicationDataContainer.cpp +++ b/dev/ApplicationData/UnpackagedApplicationDataContainer.cpp @@ -916,13 +916,12 @@ namespace Microsoft::Windows::Storage { THROW_HR_IF_MSG(E_INVALIDARG, name.empty(), "Container name not valid (%ls)", name.c_str()); + const size_t c_registryKeyNameMaxLength{ 255 }; + THROW_HR_IF_MSG(E_INVALIDARG, name.size() > c_registryKeyNameMaxLength, "Container name not valid (%ls)", name.c_str()); + for (PCWSTR s = name.c_str(); *s != L'\0'; ++s) { THROW_HR_IF_MSG(E_INVALIDARG, *s == L'\\', "Container name not valid (%ls)", name.c_str()); } - - THROW_HR_IF_MSG(E_INVALIDARG, ::Microsoft::Windows::Storage::contains_prohibited_character(name.c_str()), "Container name not valid (%ls)", name.c_str()); - THROW_HR_IF_MSG(E_INVALIDARG, CompareStringOrdinal(name.c_str(), -1, L".", -1, FALSE) == CSTR_EQUAL, "Container name not valid (%ls)", name.c_str()); - THROW_HR_IF_MSG(E_INVALIDARG, CompareStringOrdinal(name.c_str(), -1, L"..", -1, FALSE) == CSTR_EQUAL, "Container name not valid (%ls)", name.c_str()); } } diff --git a/test/ApplicationData/UnpackagedApplicationDataTests.cpp b/test/ApplicationData/UnpackagedApplicationDataTests.cpp index 3b14f683fb..69100d937f 100644 --- a/test/ApplicationData/UnpackagedApplicationDataTests.cpp +++ b/test/ApplicationData/UnpackagedApplicationDataTests.cpp @@ -53,9 +53,9 @@ namespace Test::ApplicationData::Tests void CreateResources() { CreateResources_FileSystem(); - // Registry resources are created by the code under test, so no need to create them here - // But delete any pre-existing in case a previous test crashed or otherwise didn't complete and clean up properly - DeleteResources_Registry(); + // Registry resources are created by the code under test, so no need to create them here + // But delete any pre-existing in case a previous test crashed or otherwise didn't complete and clean up properly + DeleteResources_Registry(); } void CreateResources_FileSystem() @@ -96,11 +96,11 @@ namespace Test::ApplicationData::Tests void DeleteResources_Registry() { - PCWSTR c_product{ L"Software\\TestApplicationData_Contoso" }; - const auto hr{ HRESULT_FROM_WIN32(::RegDeleteTreeW(HKEY_CURRENT_USER, c_product)) }; + const auto regkey{ std::filesystem::path{ L"SOFTWARE" } / Publisher.c_str() }; + const auto hr{ HRESULT_FROM_WIN32(::RegDeleteTreeW(HKEY_CURRENT_USER, regkey.c_str())) }; if (!wil::reg::is_registry_not_found(hr)) { - VERIFY_SUCCEEDED(hr, WEX::Common::String().Format(L"RegDeleteTreeW HKEY_CURRENT_USER\\%s", c_product)); + VERIFY_SUCCEEDED(hr, WEX::Common::String().Format(L"RegDeleteTreeW HKEY_CURRENT_USER\\%s", regkey.c_str())); } } @@ -117,9 +117,6 @@ namespace Test::ApplicationData::Tests TEST_METHOD(GetForUnpackaged_InvalidParameter) { - const winrt::hstring publisher{ L"FabrikamTest" }; - const winrt::hstring product{ L"ApplicationDataTest" }; - constexpr static PCWSTR c_invalidIds[]{ L"", L"foo/bar", @@ -465,6 +462,104 @@ namespace Test::ApplicationData::Tests } } + TEST_METHOD(CreateContainer_InvalidParameter) + { + auto applicationData{ winrt::Microsoft::Windows::Storage::ApplicationData::GetForUnpackaged(Publisher, Product) }; + VERIFY_IS_NOT_NULL(applicationData); + auto localSettings{ applicationData.LocalSettings() }; + VERIFY_IS_NOT_NULL(localSettings); + + const winrt::hstring empty; + constexpr auto disposition{ winrt::Microsoft::Windows::Storage::ApplicationDataCreateDisposition::Always }; + try + { + [[maybe_unused]] auto container{ localSettings.CreateContainer(empty, disposition) }; + VERIFY_FAIL(WEX::Common::String().Format(L"Success is not expected -- Name:%s", empty.c_str())); + } + catch (winrt::hresult_error& e) + { + VERIFY_ARE_EQUAL(E_INVALIDARG, e.code(), WEX::Common::String().Format(L"Name:%s => 0x%X %s", empty.c_str(), e.code(), e.message().c_str())); + } + + const winrt::hstring invalid{ L"foo\\bar" }; + try + { + [[maybe_unused]] auto container{ localSettings.CreateContainer(invalid, disposition) }; + VERIFY_FAIL(WEX::Common::String().Format(L"Success is not expected -- Publisher:%s Product:%s", invalid.c_str(), Product.c_str())); + } + catch (winrt::hresult_error& e) + { + VERIFY_ARE_EQUAL(E_INVALIDARG, e.code(), WEX::Common::String().Format(L"Publisher:%s Product:%s => 0x%X %s", invalid.c_str(), Product.c_str(), e.code(), e.message().c_str())); + } + + constexpr size_t c_nameMaxLength{ 255 }; + const std::wstring nameMaxLengthString(c_nameMaxLength, L'h'); + const winrt::hstring nameMaxLength{ nameMaxLengthString.c_str() }; + auto maxLengthContainer{ localSettings.CreateContainer(nameMaxLength, disposition) }; + VERIFY_IS_NOT_NULL(maxLengthContainer); + localSettings.DeleteContainer(nameMaxLength); + + const std::wstring nameTooLongString(c_nameMaxLength + 1, L'k'); + const winrt::hstring nameTooLong{ nameTooLongString.c_str() }; + VERIFY_IS_NOT_NULL(maxLengthContainer); + try + { + [[maybe_unused]] auto container{ localSettings.CreateContainer(nameTooLong, disposition) }; + VERIFY_FAIL(WEX::Common::String().Format(L"Success is not expected -- Publisher:%s Product:%s", invalid.c_str(), Product.c_str())); + } + catch (winrt::hresult_error& e) + { + VERIFY_ARE_EQUAL(E_INVALIDARG, e.code(), WEX::Common::String().Format(L"Publisher:%s Product:%s => 0x%X %s", nameTooLong.c_str(), Product.c_str(), e.code(), e.message().c_str())); + } + } + + TEST_METHOD(DeleteContainer_InvalidParameter) + { + auto applicationData{ winrt::Microsoft::Windows::Storage::ApplicationData::GetForUnpackaged(Publisher, Product) }; + VERIFY_IS_NOT_NULL(applicationData); + auto localSettings{ applicationData.LocalSettings() }; + VERIFY_IS_NOT_NULL(localSettings); + + const winrt::hstring empty; + try + { + localSettings.DeleteContainer(empty); + VERIFY_FAIL(WEX::Common::String().Format(L"Success is not expected -- Name:%s", empty.c_str())); + } + catch (winrt::hresult_error& e) + { + VERIFY_ARE_EQUAL(E_INVALIDARG, e.code(), WEX::Common::String().Format(L"Name:%s => 0x%X %s", empty.c_str(), e.code(), e.message().c_str())); + } + + const winrt::hstring invalid{ L"foo\\bar" }; + try + { + localSettings.DeleteContainer(invalid); + VERIFY_FAIL(WEX::Common::String().Format(L"Success is not expected -- Publisher:%s Product:%s", invalid.c_str(), Product.c_str())); + } + catch (winrt::hresult_error& e) + { + VERIFY_ARE_EQUAL(E_INVALIDARG, e.code(), WEX::Common::String().Format(L"Publisher:%s Product:%s => 0x%X %s", invalid.c_str(), Product.c_str(), e.code(), e.message().c_str())); + } + + constexpr size_t c_nameMaxLength{ 255 }; + const std::wstring nameMaxLengthString(c_nameMaxLength, L'h'); + const winrt::hstring nameMaxLength{ nameMaxLengthString.c_str() }; + localSettings.DeleteContainer(nameMaxLength); + + const std::wstring nameTooLongString(c_nameMaxLength + 1, L'k'); + const winrt::hstring nameTooLong{ nameTooLongString.c_str() }; + try + { + localSettings.DeleteContainer(nameTooLong); + VERIFY_FAIL(WEX::Common::String().Format(L"Success is not expected -- Publisher:%s Product:%s", invalid.c_str(), Product.c_str())); + } + catch (winrt::hresult_error& e) + { + VERIFY_ARE_EQUAL(E_INVALIDARG, e.code(), WEX::Common::String().Format(L"Publisher:%s Product:%s => 0x%X %s", nameTooLong.c_str(), Product.c_str(), e.code(), e.message().c_str())); + } + } + TEST_METHOD(ContainerOperations) { auto applicationData{ winrt::Microsoft::Windows::Storage::ApplicationData::GetForUnpackaged(L"TestApplicationData_Contoso", L"SupermarketPointOfSale") }; From 7bd8e3a1872ffdd694d09f2bb067e329ae94397c Mon Sep 17 00:00:00 2001 From: Howard Kapustein Date: Tue, 31 Mar 2026 00:36:19 -0700 Subject: [PATCH 23/24] Fixed Container name validation --- .../UnpackagedApplicationDataContainer.cpp | 8 +++-- .../UnpackagedApplicationDataTests.cpp | 34 ++++++++++++++----- 2 files changed, 31 insertions(+), 11 deletions(-) diff --git a/dev/ApplicationData/UnpackagedApplicationDataContainer.cpp b/dev/ApplicationData/UnpackagedApplicationDataContainer.cpp index 9acdebcdeb..ce81b681eb 100644 --- a/dev/ApplicationData/UnpackagedApplicationDataContainer.cpp +++ b/dev/ApplicationData/UnpackagedApplicationDataContainer.cpp @@ -914,14 +914,18 @@ namespace Microsoft::Windows::Storage void UnpackagedApplicationDataContainer::_VerifyContainerName(winrt::hstring const& name) { - THROW_HR_IF_MSG(E_INVALIDARG, name.empty(), "Container name not valid (%ls)", name.c_str()); - + // Container name max length must be <= 255 const size_t c_registryKeyNameMaxLength{ 255 }; THROW_HR_IF_MSG(E_INVALIDARG, name.size() > c_registryKeyNameMaxLength, "Container name not valid (%ls)", name.c_str()); + // Container name cannot contain a backslash for (PCWSTR s = name.c_str(); *s != L'\0'; ++s) { THROW_HR_IF_MSG(E_INVALIDARG, *s == L'\\', "Container name not valid (%ls)", name.c_str()); } + + // Container name cannot be "." or ".." + THROW_HR_IF_MSG(E_INVALIDARG, name == L".", "Container name not valid (%ls)", name.c_str()); + THROW_HR_IF_MSG(E_INVALIDARG, name == L"..", "Container name not valid (%ls)", name.c_str()); } } diff --git a/test/ApplicationData/UnpackagedApplicationDataTests.cpp b/test/ApplicationData/UnpackagedApplicationDataTests.cpp index 69100d937f..dd228e2ecf 100644 --- a/test/ApplicationData/UnpackagedApplicationDataTests.cpp +++ b/test/ApplicationData/UnpackagedApplicationDataTests.cpp @@ -471,15 +471,8 @@ namespace Test::ApplicationData::Tests const winrt::hstring empty; constexpr auto disposition{ winrt::Microsoft::Windows::Storage::ApplicationDataCreateDisposition::Always }; - try - { - [[maybe_unused]] auto container{ localSettings.CreateContainer(empty, disposition) }; - VERIFY_FAIL(WEX::Common::String().Format(L"Success is not expected -- Name:%s", empty.c_str())); - } - catch (winrt::hresult_error& e) - { - VERIFY_ARE_EQUAL(E_INVALIDARG, e.code(), WEX::Common::String().Format(L"Name:%s => 0x%X %s", empty.c_str(), e.code(), e.message().c_str())); - } + auto container{ localSettings.CreateContainer(empty, disposition) }; + VERIFY_IS_NOT_NULL(container); const winrt::hstring invalid{ L"foo\\bar" }; try @@ -511,6 +504,29 @@ namespace Test::ApplicationData::Tests { VERIFY_ARE_EQUAL(E_INVALIDARG, e.code(), WEX::Common::String().Format(L"Publisher:%s Product:%s => 0x%X %s", nameTooLong.c_str(), Product.c_str(), e.code(), e.message().c_str())); } + + const winrt::hstring invalid{ L"." }; + try + { + [[maybe_unused]] auto container{ localSettings.CreateContainer(invalid, disposition) }; + VERIFY_FAIL(WEX::Common::String().Format(L"Success is not expected -- Publisher:%s Product:%s", invalid.c_str(), Product.c_str())); + } + catch (winrt::hresult_error& e) + { + VERIFY_ARE_EQUAL(E_INVALIDARG, e.code(), WEX::Common::String().Format(L"Publisher:%s Product:%s => 0x%X %s", invalid.c_str(), Product.c_str(), e.code(), e.message().c_str())); + } + + const winrt::hstring invalid{ L".." }; + try + { + [[maybe_unused]] auto container{ localSettings.CreateContainer(invalid, disposition) }; + VERIFY_FAIL(WEX::Common::String().Format(L"Success is not expected -- Publisher:%s Product:%s", invalid.c_str(), Product.c_str())); + } + catch (winrt::hresult_error& e) + { + VERIFY_ARE_EQUAL(E_INVALIDARG, e.code(), WEX::Common::String().Format(L"Publisher:%s Product:%s => 0x%X %s", invalid.c_str(), Product.c_str(), e.code(), e.message().c_str())); + } + } TEST_METHOD(DeleteContainer_InvalidParameter) From 3b7947c60076b990d1bd85650d871232300febfb Mon Sep 17 00:00:00 2001 From: Howard Kapustein Date: Tue, 31 Mar 2026 17:45:55 -0700 Subject: [PATCH 24/24] Fixed test bug --- .../UnpackagedApplicationDataTests.cpp | 69 +++++++++++-------- 1 file changed, 42 insertions(+), 27 deletions(-) diff --git a/test/ApplicationData/UnpackagedApplicationDataTests.cpp b/test/ApplicationData/UnpackagedApplicationDataTests.cpp index dd228e2ecf..6db73ee9c0 100644 --- a/test/ApplicationData/UnpackagedApplicationDataTests.cpp +++ b/test/ApplicationData/UnpackagedApplicationDataTests.cpp @@ -474,15 +474,15 @@ namespace Test::ApplicationData::Tests auto container{ localSettings.CreateContainer(empty, disposition) }; VERIFY_IS_NOT_NULL(container); - const winrt::hstring invalid{ L"foo\\bar" }; + const winrt::hstring invalidBackslash{ L"foo\\bar" }; try { - [[maybe_unused]] auto container{ localSettings.CreateContainer(invalid, disposition) }; - VERIFY_FAIL(WEX::Common::String().Format(L"Success is not expected -- Publisher:%s Product:%s", invalid.c_str(), Product.c_str())); + [[maybe_unused]] auto container{ localSettings.CreateContainer(invalidBackslash, disposition) }; + VERIFY_FAIL(WEX::Common::String().Format(L"Success is not expected -- Publisher:%s Product:%s", invalidBackslash.c_str(), Product.c_str())); } catch (winrt::hresult_error& e) { - VERIFY_ARE_EQUAL(E_INVALIDARG, e.code(), WEX::Common::String().Format(L"Publisher:%s Product:%s => 0x%X %s", invalid.c_str(), Product.c_str(), e.code(), e.message().c_str())); + VERIFY_ARE_EQUAL(E_INVALIDARG, e.code(), WEX::Common::String().Format(L"Publisher:%s Product:%s => 0x%X %s", invalidBackslash.c_str(), Product.c_str(), e.code(), e.message().c_str())); } constexpr size_t c_nameMaxLength{ 255 }; @@ -498,33 +498,33 @@ namespace Test::ApplicationData::Tests try { [[maybe_unused]] auto container{ localSettings.CreateContainer(nameTooLong, disposition) }; - VERIFY_FAIL(WEX::Common::String().Format(L"Success is not expected -- Publisher:%s Product:%s", invalid.c_str(), Product.c_str())); + VERIFY_FAIL(WEX::Common::String().Format(L"Success is not expected -- Publisher:%s Product:%s", nameTooLong.c_str(), Product.c_str())); } catch (winrt::hresult_error& e) { VERIFY_ARE_EQUAL(E_INVALIDARG, e.code(), WEX::Common::String().Format(L"Publisher:%s Product:%s => 0x%X %s", nameTooLong.c_str(), Product.c_str(), e.code(), e.message().c_str())); } - const winrt::hstring invalid{ L"." }; + const winrt::hstring invalidDot{ L"." }; try { - [[maybe_unused]] auto container{ localSettings.CreateContainer(invalid, disposition) }; - VERIFY_FAIL(WEX::Common::String().Format(L"Success is not expected -- Publisher:%s Product:%s", invalid.c_str(), Product.c_str())); + [[maybe_unused]] auto container{ localSettings.CreateContainer(invalidDot, disposition) }; + VERIFY_FAIL(WEX::Common::String().Format(L"Success is not expected -- Publisher:%s Product:%s", invalidDot.c_str(), Product.c_str())); } catch (winrt::hresult_error& e) { - VERIFY_ARE_EQUAL(E_INVALIDARG, e.code(), WEX::Common::String().Format(L"Publisher:%s Product:%s => 0x%X %s", invalid.c_str(), Product.c_str(), e.code(), e.message().c_str())); + VERIFY_ARE_EQUAL(E_INVALIDARG, e.code(), WEX::Common::String().Format(L"Publisher:%s Product:%s => 0x%X %s", invalidDot.c_str(), Product.c_str(), e.code(), e.message().c_str())); } - const winrt::hstring invalid{ L".." }; + const winrt::hstring invalidDotDot{ L".." }; try { - [[maybe_unused]] auto container{ localSettings.CreateContainer(invalid, disposition) }; - VERIFY_FAIL(WEX::Common::String().Format(L"Success is not expected -- Publisher:%s Product:%s", invalid.c_str(), Product.c_str())); + [[maybe_unused]] auto container{ localSettings.CreateContainer(invalidDotDot, disposition) }; + VERIFY_FAIL(WEX::Common::String().Format(L"Success is not expected -- Publisher:%s Product:%s", invalidDotDot.c_str(), Product.c_str())); } catch (winrt::hresult_error& e) { - VERIFY_ARE_EQUAL(E_INVALIDARG, e.code(), WEX::Common::String().Format(L"Publisher:%s Product:%s => 0x%X %s", invalid.c_str(), Product.c_str(), e.code(), e.message().c_str())); + VERIFY_ARE_EQUAL(E_INVALIDARG, e.code(), WEX::Common::String().Format(L"Publisher:%s Product:%s => 0x%X %s", invalidDotDot.c_str(), Product.c_str(), e.code(), e.message().c_str())); } } @@ -537,25 +537,17 @@ namespace Test::ApplicationData::Tests VERIFY_IS_NOT_NULL(localSettings); const winrt::hstring empty; - try - { - localSettings.DeleteContainer(empty); - VERIFY_FAIL(WEX::Common::String().Format(L"Success is not expected -- Name:%s", empty.c_str())); - } - catch (winrt::hresult_error& e) - { - VERIFY_ARE_EQUAL(E_INVALIDARG, e.code(), WEX::Common::String().Format(L"Name:%s => 0x%X %s", empty.c_str(), e.code(), e.message().c_str())); - } + localSettings.DeleteContainer(empty); - const winrt::hstring invalid{ L"foo\\bar" }; + const winrt::hstring invalidBackslash{ L"foo\\bar" }; try { - localSettings.DeleteContainer(invalid); - VERIFY_FAIL(WEX::Common::String().Format(L"Success is not expected -- Publisher:%s Product:%s", invalid.c_str(), Product.c_str())); + localSettings.DeleteContainer(invalidBackslash); + VERIFY_FAIL(WEX::Common::String().Format(L"Success is not expected -- Publisher:%s Product:%s", invalidBackslash.c_str(), Product.c_str())); } catch (winrt::hresult_error& e) { - VERIFY_ARE_EQUAL(E_INVALIDARG, e.code(), WEX::Common::String().Format(L"Publisher:%s Product:%s => 0x%X %s", invalid.c_str(), Product.c_str(), e.code(), e.message().c_str())); + VERIFY_ARE_EQUAL(E_INVALIDARG, e.code(), WEX::Common::String().Format(L"Publisher:%s Product:%s => 0x%X %s", invalidBackslash.c_str(), Product.c_str(), e.code(), e.message().c_str())); } constexpr size_t c_nameMaxLength{ 255 }; @@ -568,12 +560,35 @@ namespace Test::ApplicationData::Tests try { localSettings.DeleteContainer(nameTooLong); - VERIFY_FAIL(WEX::Common::String().Format(L"Success is not expected -- Publisher:%s Product:%s", invalid.c_str(), Product.c_str())); + VERIFY_FAIL(WEX::Common::String().Format(L"Success is not expected -- Publisher:%s Product:%s", nameTooLong.c_str(), Product.c_str())); } catch (winrt::hresult_error& e) { VERIFY_ARE_EQUAL(E_INVALIDARG, e.code(), WEX::Common::String().Format(L"Publisher:%s Product:%s => 0x%X %s", nameTooLong.c_str(), Product.c_str(), e.code(), e.message().c_str())); } + + const winrt::hstring invalidDot{ L"." }; + try + { + localSettings.DeleteContainer(invalidDot); + VERIFY_FAIL(WEX::Common::String().Format(L"Success is not expected -- Publisher:%s Product:%s", invalidDot.c_str(), Product.c_str())); + } + catch (winrt::hresult_error& e) + { + VERIFY_ARE_EQUAL(E_INVALIDARG, e.code(), WEX::Common::String().Format(L"Publisher:%s Product:%s => 0x%X %s", invalidDot.c_str(), Product.c_str(), e.code(), e.message().c_str())); + } + + const winrt::hstring invalidDotDot{ L".." }; + try + { + localSettings.DeleteContainer(invalidDotDot); + VERIFY_FAIL(WEX::Common::String().Format(L"Success is not expected -- Publisher:%s Product:%s", invalidDotDot.c_str(), Product.c_str())); + } + catch (winrt::hresult_error& e) + { + VERIFY_ARE_EQUAL(E_INVALIDARG, e.code(), WEX::Common::String().Format(L"Publisher:%s Product:%s => 0x%X %s", invalidDotDot.c_str(), Product.c_str(), e.code(), e.message().c_str())); + } + } TEST_METHOD(ContainerOperations)