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/ApplicationData.vcxitems b/dev/ApplicationData/ApplicationData.vcxitems index 32e3851ac8..3655db8ddf 100644 --- a/dev/ApplicationData/ApplicationData.vcxitems +++ b/dev/ApplicationData/ApplicationData.vcxitems @@ -7,7 +7,7 @@ - %(AdditionalIncludeDirectories);$(MSBuildThisFileDirectory) + %(AdditionalIncludeDirectories);$(MSBuildThisFileDirectory);$(RepoRoot)\dev\common @@ -16,12 +16,17 @@ + + + + + diff --git a/dev/ApplicationData/ApplicationData.vcxitems.filters b/dev/ApplicationData/ApplicationData.vcxitems.filters index 215fe7b0dc..b0a21278ad 100644 --- a/dev/ApplicationData/ApplicationData.vcxitems.filters +++ b/dev/ApplicationData/ApplicationData.vcxitems.filters @@ -17,6 +17,12 @@ Source Files + + Source Files + + + Source Files + @@ -31,6 +37,15 @@ Header Files + + Header Files + + + Header Files + + + Header Files + diff --git a/dev/ApplicationData/ApplicationDataTelemetry.h b/dev/ApplicationData/ApplicationDataTelemetry.h index 9ce1d58fc5..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 @@ -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/M.W.S.ApplicationData.cpp b/dev/ApplicationData/M.W.S.ApplicationData.cpp index 89ecdecb0d..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)); @@ -21,11 +23,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 +101,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,18 +109,30 @@ 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(); + _VerifyPublisher(publisher); + _VerifyProduct(product); + + return winrt::make(publisher, product); } 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 +141,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 +154,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 +187,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 +200,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 +213,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 +226,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 +240,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 +253,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 +266,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 +280,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 +300,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 +313,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 +325,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 +338,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 +366,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 +401,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 }; @@ -312,4 +415,14 @@ namespace winrt::Microsoft::Windows::Storage::implementation } return path; } + void ApplicationData::_VerifyPublisher(winrt::hstring const& string) + { + 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()); + } + void ApplicationData::_VerifyProduct(winrt::hstring const& string) + { + 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 8256e50701..11bb311a84 100644 --- a/dev/ApplicationData/M.W.S.ApplicationData.h +++ b/dev/ApplicationData/M.W.S.ApplicationData.h @@ -5,12 +5,16 @@ #include "Microsoft.Windows.Storage.ApplicationData.g.h" +#include "UnpackagedApplicationData.h" + 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); @@ -39,8 +43,11 @@ 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 void _VerifyPublisher(winrt::hstring const& string); + static void _VerifyProduct(winrt::hstring const& string); 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/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/UnpackagedApplicationData.cpp b/dev/ApplicationData/UnpackagedApplicationData.cpp new file mode 100644 index 0000000000..e1d3ba000f --- /dev/null +++ b/dev/ApplicationData/UnpackagedApplicationData.cpp @@ -0,0 +1,275 @@ +// Copyright (c) Microsoft Corporation and Contributors. +// Licensed under the MIT License. + +#include "pch.h" + +#include + +#include + +#include "UnpackagedApplicationData.h" +#include "UnpackagedApplicationDataContainer.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_temporaryPath() + { + } + 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... + // 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(); + } + return m_localPath; + } + 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... + auto path{ ::Microsoft::FileSystem::Path::GetTempDirectory() }; + path /= m_publisher.c_str(); + path /= m_product.c_str(); + m_temporaryPath = path.c_str(); + } + return m_temporaryPath; + } + winrt::Windows::Storage::StorageFolder UnpackagedApplicationData::LocalCacheFolder() + { + _VerifyNotClosed(); + + const auto path{ LocalCachePath() }; + if (path.empty()) + { + return nullptr; + } + return winrt::Windows::Storage::StorageFolder::GetFolderFromPathAsync(path).get(); + } + winrt::Windows::Storage::StorageFolder UnpackagedApplicationData::LocalFolder() + { + _VerifyNotClosed(); + + const auto path{ LocalPath() }; + if (path.empty()) + { + return nullptr; + } + return winrt::Windows::Storage::StorageFolder::GetFolderFromPathAsync(path).get(); + } + winrt::Windows::Storage::StorageFolder UnpackagedApplicationData::MachineFolder() + { + _VerifyNotClosed(); + + const auto path{ MachinePath() }; + if (path.empty()) + { + return nullptr; + } + return winrt::Windows::Storage::StorageFolder::GetFolderFromPathAsync(path).get(); + } + winrt::Windows::Storage::StorageFolder UnpackagedApplicationData::SharedLocalFolder() + { + _VerifyNotClosed(); + + const auto path{ SharedLocalPath() }; + if (path.empty()) + { + return nullptr; + } + return winrt::Windows::Storage::StorageFolder::GetFolderFromPathAsync(path).get(); + } + winrt::Windows::Storage::StorageFolder UnpackagedApplicationData::TemporaryFolder() + { + _VerifyNotClosed(); + + const auto path{ TemporaryPath() }; + if (path.empty()) + { + return nullptr; + } + return winrt::Windows::Storage::StorageFolder::GetFolderFromPathAsync(path).get(); + } + winrt::Microsoft::Windows::Storage::ApplicationDataContainer UnpackagedApplicationData::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) + { + _VerifyNotClosed(); + + 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_NOTIMPL, "%d", static_cast(locality)); + } + + auto logTelemetry{ ApplicationDataTelemetry::UnpackagedClearAsync::Start(locality, publisher, product) }; + + logTelemetry.IgnoreCurrentThread(); + co_await winrt::resume_background(); + auto logTelemetryContinuation{ logTelemetry.ContinueOnCurrentThread() }; + + // Clear the path + if (!localityPath.empty()) + { + std::filesystem::path path{ localityPath.c_str() }; + if (_PathExists(path)) + { + const auto options{ wil::RemoveDirectoryOptions::KeepRootDirectory | wil::RemoveDirectoryOptions::RemoveReadOnly }; + THROW_IF_FAILED(wil::RemoveDirectoryRecursiveNoThrow(path.c_str(), options)); + } + } + + // 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\\{}\\{}", 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); + } + } + + 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() + { + m_publisher.clear(); + m_product.clear(); + m_localPath.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(); + } + void UnpackagedApplicationData::_VerifyNotClosed() + { + THROW_HR_IF(RO_E_CLOSED, m_publisher.empty()); + } + std::filesystem::path UnpackagedApplicationData::_MachinePath(winrt::hstring const& publisher, winrt::hstring const& product) + { + 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(); + + // Does it exist? + if (_PathExists(path)) + { + return path; + } + return std::filesystem::path{}; + } + + bool UnpackagedApplicationData::_PathExists(std::filesystem::path const& path) + { + const std::filesystem::directory_entry directoryEntry{ path }; + return directoryEntry.is_directory(); + } +} diff --git a/dev/ApplicationData/UnpackagedApplicationData.h b/dev/ApplicationData/UnpackagedApplicationData.h new file mode 100644 index 0000000000..4424cde99d --- /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: + void _VerifyNotClosed(); + 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; + winrt::hstring m_temporaryPath; + }; +} diff --git a/dev/ApplicationData/UnpackagedApplicationDataContainer.cpp b/dev/ApplicationData/UnpackagedApplicationDataContainer.cpp new file mode 100644 index 0000000000..ce81b681eb --- /dev/null +++ b/dev/ApplicationData/UnpackagedApplicationDataContainer.cpp @@ -0,0 +1,931 @@ +// Copyright (c) Microsoft Corporation and Contributors. +// Licensed under the MIT License. + +#include "pch.h" + +#include "UnpackagedApplicationDataContainer.h" + +#include "Validate.h" + +namespace Microsoft::Windows::Storage +{ + namespace + { + // 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 + winrt::com_array ReadCustomTypeArrayValue(HKEY key, PCWSTR valueName, DWORD dataSize) + { + 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) + { + DWORD type{}; + DWORD dataSize{}; + const auto status{ ::RegQueryValueExW(key, valueName, nullptr, &type, nullptr, &dataSize) }; + THROW_IF_WIN32_ERROR(status); + + switch (type) + { + case REG_NONE: + return nullptr; + 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_MULTI_SZ: + { + 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') + { + auto len{ wcsnlen(p, static_cast(end - p)) }; + winrt::hstring str{ p, static_cast(len) }; + strings.push_back(str); + p += str.size() + 1; + } + 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) + { + arr[i] = static_cast(bytes[i]); + } + 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) + { + arr[i] = winrt::Windows::Foundation::DateTime{ winrt::Windows::Foundation::TimeSpan{ ticks[i] } }; + } + 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) + { + 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); + } + } + + 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) }; + 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) }; + SetCustomTypeValue(key, valueName, ApplicationDataRegistryType_UInt8, val); + break; + } + case winrt::Windows::Foundation::PropertyType::Int16: + { + auto val{ winrt::unbox_value(value) }; + SetCustomTypeValue(key, valueName, ApplicationDataRegistryType_Int16, val); + break; + } + case winrt::Windows::Foundation::PropertyType::UInt16: + { + auto val{ winrt::unbox_value(value) }; + SetCustomTypeValue(key, valueName, ApplicationDataRegistryType_UInt16, val); + break; + } + case winrt::Windows::Foundation::PropertyType::Int32: + { + auto val{ winrt::unbox_value(value) }; + SetCustomTypeValue(key, valueName, ApplicationDataRegistryType_Int32, val); + break; + } + case winrt::Windows::Foundation::PropertyType::Int64: + { + auto val{ winrt::unbox_value(value) }; + SetCustomTypeValue(key, valueName, ApplicationDataRegistryType_Int64, val); + break; + } + case winrt::Windows::Foundation::PropertyType::Single: + { + auto val{ winrt::unbox_value(value) }; + SetCustomTypeValue(key, valueName, ApplicationDataRegistryType_Single, val); + break; + } + case winrt::Windows::Foundation::PropertyType::Double: + { + auto val{ winrt::unbox_value(value) }; + SetCustomTypeValue(key, valueName, ApplicationDataRegistryType_Double, val); + break; + } + case winrt::Windows::Foundation::PropertyType::Char16: + { + auto val{ winrt::unbox_value(value) }; + 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) }; + SetCustomTypeValue(key, valueName, ApplicationDataRegistryType_Boolean, byte); + break; + } + case winrt::Windows::Foundation::PropertyType::DateTime: + { + auto val{ propertyValue.GetDateTime() }; + auto ticks{ val.time_since_epoch().count() }; + SetCustomTypeValue(key, valueName, ApplicationDataRegistryType_DateTime, ticks); + break; + } + case winrt::Windows::Foundation::PropertyType::TimeSpan: + { + auto val{ propertyValue.GetTimeSpan() }; + auto duration{ val.count() }; + SetCustomTypeValue(key, valueName, ApplicationDataRegistryType_TimeSpan, duration); + break; + } + case winrt::Windows::Foundation::PropertyType::Guid: + { + auto val{ propertyValue.GetGuid() }; + SetCustomTypeValue(key, valueName, ApplicationDataRegistryType_Guid, val); + break; + } + case winrt::Windows::Foundation::PropertyType::Point: + { + auto val{ propertyValue.GetPoint() }; + SetCustomTypeValue(key, valueName, ApplicationDataRegistryType_Point, val); + break; + } + case winrt::Windows::Foundation::PropertyType::Size: + { + auto val{ propertyValue.GetSize() }; + SetCustomTypeValue(key, valueName, ApplicationDataRegistryType_Size, val); + break; + } + case winrt::Windows::Foundation::PropertyType::Rect: + { + auto val{ propertyValue.GetRect() }; + 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: + 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 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 + { + 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 + LOG_CAUGHT_EXCEPTION(); + } + } + 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() + { + // 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{})) + { + names.push_back(valueData.name); + } + for (const auto& name : names) + { + 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( + FAILED(hrFirst) ? + winrt::Windows::Foundation::Collections::CollectionChange::ItemRemoved : + winrt::Windows::Foundation::Collections::CollectionChange::Reset, + winrt::hstring{})); + THROW_IF_FAILED(hrFirst); + } + + // 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 + LOG_CAUGHT_EXCEPTION(); + } + } + 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() + { + _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{})) + { + 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() + { + _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); + } + + void UnpackagedApplicationDataContainer::Close() + { + m_key.reset(); + } + + winrt::Microsoft::Windows::Storage::ApplicationDataContainer UnpackagedApplicationDataContainer::CreateContainer( + winrt::hstring const& name, + winrt::Microsoft::Windows::Storage::ApplicationDataCreateDisposition const& disposition) + { + _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) }; + 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) + { + _VerifyNotClosed(); + + _VerifyContainerName(name); + + const auto hr{ HRESULT_FROM_WIN32(::RegDeleteTreeW(m_key.get(), name.c_str())) }; + if (SUCCEEDED(hr)) + { + return; + } + else if (!wil::reg::is_registry_not_found(hr)) + { + THROW_HR_MSG(hr, "%ls", name.c_str()); + } + } + + void UnpackagedApplicationDataContainer::_VerifyNotClosed() + { + THROW_HR_IF_NULL(RO_E_CLOSED, m_key); + } + + void UnpackagedApplicationDataContainer::_VerifyContainerName(winrt::hstring const& name) + { + // 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/dev/ApplicationData/UnpackagedApplicationDataContainer.h b/dev/ApplicationData/UnpackagedApplicationDataContainer.h new file mode 100644 index 0000000000..b6e660da9a --- /dev/null +++ b/dev/ApplicationData/UnpackagedApplicationDataContainer.h @@ -0,0 +1,32 @@ +// 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: + void _VerifyNotClosed(); + void _VerifyContainerName(winrt::hstring const& name); + + private: + wil::shared_hkey m_key; + winrt::hstring m_name; + winrt::Microsoft::Windows::Storage::ApplicationDataLocality m_locality{}; + }; +} diff --git a/dev/ApplicationData/Validate.h b/dev/ApplicationData/Validate.h new file mode 100644 index 0000000000..cc80fb01e0 --- /dev/null +++ b/dev/ApplicationData/Validate.h @@ -0,0 +1,99 @@ +// Copyright (c) Microsoft Corporation and Contributors. +// Licensed under the MIT License. + +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 +inline 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); +} + +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" +}; + +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--" +}; + +inline wchar_t ascii_tolower_ordinal(const wchar_t c) noexcept +{ + return (c >= L'A' && c <= L'Z') ? (c | 0x20) : c; +} + +inline 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; + } + } +} + +inline bool contains_prohibited_character(_In_ PCWSTR string) +{ + for (PCWSTR s = string; *s != L'\0'; ++s) + { + if (!is_valid_character(*s)) + { + return true; + } + } + return false; +} + +inline bool is_prohibited_string(_In_ PCWSTR string) +{ + // Contains prohibited character(s) + if (contains_prohibited_character(string)) + { + return true; + } + + // 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 true; + } + } + + // Starts with a prohibited string + for (size_t index = 0; index < ARRAYSIZE(c_prohibitedStringsStartsWith); ++index) + { + if (startswith_ordinal_nocase(string, c_prohibitedStringsStartsWith[index])) + { + return true; + } + } + + return false; +} +} diff --git a/dev/ApplicationData/pch.h b/dev/ApplicationData/pch.h index 786111d0d5..b9095c50cb 100644 --- a/dev/ApplicationData/pch.h +++ b/dev/ApplicationData/pch.h @@ -18,6 +18,7 @@ #include #include #include +#include #include #include @@ -28,4 +29,9 @@ #include #include +#include +#include +#include #include + +#include "Validate.h" diff --git a/dev/Common/Common.vcxitems b/dev/Common/Common.vcxitems index 0da4fd227a..8091baaca7 100644 --- a/dev/Common/Common.vcxitems +++ b/dev/Common/Common.vcxitems @@ -23,8 +23,13 @@ + + + + + diff --git a/dev/Common/Common.vcxitems.filters b/dev/Common/Common.vcxitems.filters index 987c2340a5..3dabc04cff 100644 --- a/dev/Common/Common.vcxitems.filters +++ b/dev/Common/Common.vcxitems.filters @@ -23,31 +23,46 @@ 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 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..f1ec626873 100644 --- a/dev/WindowsAppRuntime_DLL/pch.h +++ b/dev/WindowsAppRuntime_DLL/pch.h @@ -56,6 +56,8 @@ #include #include #include +#include +#include #include #include #include diff --git a/test/ApplicationData/ApplicationDataTests.cpp b/test/ApplicationData/ApplicationDataTests.cpp index e48649be5e..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() @@ -49,12 +48,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 +85,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 +425,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/ApplicationDataTests.vcxproj b/test/ApplicationData/ApplicationDataTests.vcxproj index e511f44ecd..261cef4581 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_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/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..6db73ee9c0 --- /dev/null +++ b/test/ApplicationData/UnpackagedApplicationDataTests.cpp @@ -0,0 +1,1249 @@ +// 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 auto Main_PackageFamilyName{ ::TP::DynamicDependencyDataStore::c_PackageFamilyName }; + + 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"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() + { + 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()), + WEX::Common::String().Format(L"MKDIR %s", localPath.c_str())); + + 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() + { + DeleteResources_FileSystem(); + DeleteResources_Registry(); + } + + void DeleteResources_FileSystem() + { + 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 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())); + } + } + + void DeleteResources_Registry() + { + 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", regkey.c_str())); + } + } + + 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) + { + 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) + { + const winrt::hstring invalid{ invalidId }; + try + { + [[maybe_unused]] auto applicationData{ winrt::Microsoft::Windows::Storage::ApplicationData::GetForUnpackaged(invalid, Product) }; + 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())); + } + + try + { + [[maybe_unused]] auto applicationData{ winrt::Microsoft::Windows::Storage::ApplicationData::GetForUnpackaged(Publisher, invalid) }; + 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) + { + 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) }; + VERIFY_IS_NOT_NULL(applicationData); + } + + 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) + { + // Caller is LocalSystem : %PROGRAMDATA%\...publisher... + // Caller is =MediumIL : %USERPROFILE%\AppData\Local\...publisher... + 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; + } + + 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() }; + } + + 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(); + } + + std::filesystem::path UserTemporaryPath(winrt::hstring const& publisher) + { + // 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) + { + // GetTempPath[2]() + \...publisher...\...product... + auto path{ UserTemporaryPath(publisher) }; + 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(LocalCacheFolderAndPath) + { + 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())); + } + } + + 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 localFolderPath{ ToLongPath(localFolder.Path()) }; + const auto localPath{ ToLongPath(applicationData.LocalPath()) }; + VERIFY_ARE_EQUAL(localFolderPath, localPath); + const auto expectedLocalFolder{ UserLocalFolder(Publisher, Product) }; + const auto expectedLocalPath{ ToLongPath(UserLocalPath(Publisher, Product).c_str()) }; + VERIFY_ARE_EQUAL(localPath, expectedLocalPath); + } + + TEST_METHOD(SharedLocalFolderAndPath) + { + auto applicationData{ winrt::Microsoft::Windows::Storage::ApplicationData::GetForUnpackaged(Publisher, Product) }; + VERIFY_IS_NOT_NULL(applicationData); + + 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())); + } + } + + 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 temporaryFolderPath{ ToLongPath(temporaryFolder.Path()) }; + 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{ ToLongPath(UserTemporaryPath(Publisher, Product)) }; + VERIFY_ARE_EQUAL(temporaryPath, expectedTemporaryPath, WEX::Common::String().Format(L"Expected:%ls Actual:%ls", expectedTemporaryPath.c_str(), temporaryPath.c_str())); + } + + 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 machinePath{ applicationData.MachinePath() }; + VERIFY_ARE_EQUAL(machinePath, null_hstring); + const auto machineFolder{ applicationData.MachineFolder() }; + VERIFY_IS_NULL(machineFolder); + } + + TEST_METHOD(LocalSettings) + { + winrt::hstring packageFamilyName{ Main_PackageFamilyName }; + auto applicationData{ winrt::Microsoft::Windows::Storage::ApplicationData::GetForUnpackaged(Publisher, Product) }; + VERIFY_IS_NOT_NULL(applicationData); + + const auto localSettings{ applicationData.LocalSettings() }; + VERIFY_ARE_EQUAL(winrt::Microsoft::Windows::Storage::ApplicationDataLocality::Local, localSettings.Locality()); + + auto containers{ localSettings.Containers() }; + VERIFY_ARE_EQUAL(0u, containers.Size()); + + const winrt::hstring foodAndStuff{ L"FoodAndStuff" }; + VERIFY_IS_FALSE(containers.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()); + + 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); + + const winrt::hstring keyDrink{ L"Drink" }; + const winrt::hstring rawValueWhiskey{ L"Whiskey" }; + auto valueWhiskey{ winrt::Windows::Foundation::PropertyValue::CreateString(rawValueWhiskey) }; + 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); + 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(1u, localSettings.Containers().Size()); + localSettings.DeleteContainer(foodAndStuff); + VERIFY_ARE_EQUAL(0u, localSettings.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())); + } + } + + TEST_METHOD(ClearAsync_LocalPath) + { + //TODO ClearAsync_LocalPath + } + + TEST_METHOD(ClearAsync_LocalSettings) + { + //TODO ClearAsync_LocalSettings + } + + TEST_METHOD(ClearAsync_Local) + { + //TODO ClearAsync_Local + } + + TEST_METHOD(ClearAsync_MachinePath) + { + //TODO ClearAsync_MachinePath + } + + TEST_METHOD(ClearPublisherCacheFolderAsync) + { + auto applicationData{ winrt::Microsoft::Windows::Storage::ApplicationData::GetForUnpackaged(Publisher, Product) }; + VERIFY_IS_NOT_NULL(applicationData); + + 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(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 }; + auto container{ localSettings.CreateContainer(empty, disposition) }; + VERIFY_IS_NOT_NULL(container); + + const winrt::hstring invalidBackslash{ L"foo\\bar" }; + try + { + [[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", invalidBackslash.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", 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 + { + [[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", invalidDot.c_str(), Product.c_str(), e.code(), e.message().c_str())); + } + + const winrt::hstring invalidDotDot{ L".." }; + try + { + [[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", invalidDotDot.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; + localSettings.DeleteContainer(empty); + + const winrt::hstring invalidBackslash{ L"foo\\bar" }; + try + { + 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", invalidBackslash.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", 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) + { + 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(u'Z')); + VERIFY_ARE_EQUAL(u'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(); + + using PV = winrt::Windows::Foundation::PropertyValue; + namespace WF = winrt::Windows::Foundation; + + // UInt8Array + { + // 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; + 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 + { + // 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; + 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 + { + // 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; + 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"); + } + }; +} diff --git a/test/ApplicationData/pch.h b/test/ApplicationData/pch.h index 009cc61028..2837841ab5 100644 --- a/test/ApplicationData/pch.h +++ b/test/ApplicationData/pch.h @@ -11,6 +11,8 @@ #include #include +#include +#include #include #include @@ -25,6 +27,9 @@ #include #include +#include +#include +#include #include #include 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