From aae3caa1a96b82c4dea52d67a4f9e94c0dcf1bf9 Mon Sep 17 00:00:00 2001 From: --global Date: Tue, 26 Nov 2024 16:13:36 -0800 Subject: [PATCH 01/13] add initial flow for installing a font --- .../v1.10.0/manifest.installer.1.10.0.json | 6 ++- .../v1.10.0/manifest.singleton.1.10.0.json | 6 ++- .../AppInstallerCLICore.vcxproj | 2 + .../AppInstallerCLICore.vcxproj.filters | 6 +++ .../Commands/FontCommand.cpp | 49 +++++++++++++++++++ .../Commands/FontCommand.h | 18 +++++++ src/AppInstallerCLICore/FontInstaller.cpp | 25 ++++++++++ src/AppInstallerCLICore/FontInstaller.h | 21 ++++++++ src/AppInstallerCLICore/Resources.h | 2 + .../Workflows/DownloadFlow.cpp | 1 + .../Workflows/FontFlow.cpp | 4 ++ src/AppInstallerCLICore/Workflows/FontFlow.h | 12 +++++ .../Workflows/InstallFlow.cpp | 14 ++++++ .../Workflows/InstallFlow.h | 6 +++ .../Shared/Strings/en-us/winget.resw | 12 +++-- .../Public/winget/ManifestCommon.h | 1 + .../Public/AppInstallerErrors.h | 1 + .../PackageManager.idl | 2 + 18 files changed, 181 insertions(+), 7 deletions(-) create mode 100644 src/AppInstallerCLICore/FontInstaller.cpp create mode 100644 src/AppInstallerCLICore/FontInstaller.h diff --git a/schemas/JSON/manifests/v1.10.0/manifest.installer.1.10.0.json b/schemas/JSON/manifests/v1.10.0/manifest.installer.1.10.0.json index c64358a9e0..fc5281d36b 100644 --- a/schemas/JSON/manifests/v1.10.0/manifest.installer.1.10.0.json +++ b/schemas/JSON/manifests/v1.10.0/manifest.installer.1.10.0.json @@ -65,7 +65,8 @@ "wix", "burn", "pwa", - "portable" + "portable", + "font" ], "description": "Enumeration of supported installer types. InstallerType is required in either root level or individual Installer level" }, @@ -80,7 +81,8 @@ "nullsoft", "wix", "burn", - "portable" + "portable", + "font" ], "description": "Enumeration of supported nested installer types contained inside an archive file" }, diff --git a/schemas/JSON/manifests/v1.10.0/manifest.singleton.1.10.0.json b/schemas/JSON/manifests/v1.10.0/manifest.singleton.1.10.0.json index 084bafedd5..1e66baf7c3 100644 --- a/schemas/JSON/manifests/v1.10.0/manifest.singleton.1.10.0.json +++ b/schemas/JSON/manifests/v1.10.0/manifest.singleton.1.10.0.json @@ -167,7 +167,8 @@ "wix", "burn", "pwa", - "portable" + "portable", + "font" ], "description": "Enumeration of supported installer types. InstallerType is required in either root level or individual Installer level" }, @@ -182,7 +183,8 @@ "nullsoft", "wix", "burn", - "portable" + "portable", + "font" ], "description": "Enumeration of supported nested installer types contained inside an archive file" }, diff --git a/src/AppInstallerCLICore/AppInstallerCLICore.vcxproj b/src/AppInstallerCLICore/AppInstallerCLICore.vcxproj index 45c9c0fa04..cfff631fb3 100644 --- a/src/AppInstallerCLICore/AppInstallerCLICore.vcxproj +++ b/src/AppInstallerCLICore/AppInstallerCLICore.vcxproj @@ -398,6 +398,7 @@ + @@ -483,6 +484,7 @@ + Create diff --git a/src/AppInstallerCLICore/AppInstallerCLICore.vcxproj.filters b/src/AppInstallerCLICore/AppInstallerCLICore.vcxproj.filters index a8f48edd34..e5942dd17c 100644 --- a/src/AppInstallerCLICore/AppInstallerCLICore.vcxproj.filters +++ b/src/AppInstallerCLICore/AppInstallerCLICore.vcxproj.filters @@ -266,6 +266,9 @@ Workflows + + Header Files + @@ -502,6 +505,9 @@ Workflows + + Source Files + diff --git a/src/AppInstallerCLICore/Commands/FontCommand.cpp b/src/AppInstallerCLICore/Commands/FontCommand.cpp index c6faae5b44..2804e7aabf 100644 --- a/src/AppInstallerCLICore/Commands/FontCommand.cpp +++ b/src/AppInstallerCLICore/Commands/FontCommand.cpp @@ -5,6 +5,7 @@ #include "Workflows/CompletionFlow.h" #include "Workflows/WorkflowBase.h" #include "Workflows/FontFlow.h" +#include "Workflows/InstallFlow.h" #include "Resources.h" namespace AppInstaller::CLI @@ -43,6 +44,54 @@ namespace AppInstaller::CLI OutputHelp(context.Reporter); } + std::vector FontInstallCommand::GetArguments() const + { + return { + Argument::ForType(Args::Type::Manifest), + Argument{ Args::Type::InstallScope, Resource::String::InstallScopeDescription, ArgumentType::Standard, Argument::Visibility::Help }, + Argument::ForType(Args::Type::Force), + }; + } + + Resource::LocString FontInstallCommand::ShortDescription() const + { + return { Resource::String::FontInstallCommandShortDescription }; + } + + Resource::LocString FontInstallCommand::LongDescription() const + { + return { Resource::String::FontInstallCommandLongDescription }; + } + + void FontInstallCommand::Complete(Execution::Context& context, Args::Type valueType) const + { + UNREFERENCED_PARAMETER(valueType); + context.Reporter.Error() << Resource::String::PendingWorkError << std::endl; + THROW_HR(E_NOTIMPL); + } + + Utility::LocIndView FontInstallCommand::HelpLink() const + { + return s_FontCommand_HelpLink; + } + + void FontInstallCommand::ValidateArgumentsInternal(Execution::Args& execArgs) const + { + Argument::ValidateCommonArguments(execArgs); + } + + void FontInstallCommand::ExecuteInternal(Execution::Context& context) const + { + if (context.Args.Contains(Execution::Args::Type::Manifest)) + { + context << + Workflow::ReportExecutionStage(ExecutionStage::Discovery) << + Workflow::GetManifestFromArg << + Workflow::SelectInstaller << + Workflow::InstallSinglePackage; + } + } + std::vector FontListCommand::GetArguments() const { return { diff --git a/src/AppInstallerCLICore/Commands/FontCommand.h b/src/AppInstallerCLICore/Commands/FontCommand.h index c29f703f6c..741821cd9c 100644 --- a/src/AppInstallerCLICore/Commands/FontCommand.h +++ b/src/AppInstallerCLICore/Commands/FontCommand.h @@ -21,6 +21,24 @@ namespace AppInstaller::CLI void ExecuteInternal(Execution::Context& context) const override; }; + struct FontInstallCommand final : public Command + { + FontInstallCommand(std::string_view parent) : Command("install", parent) {} + + std::vector GetArguments() const override; + + Resource::LocString ShortDescription() const override; + Resource::LocString LongDescription() const override; + + void Complete(Execution::Context& context, Execution::Args::Type valueType) const override; + + Utility::LocIndView HelpLink() const override; + + protected: + void ValidateArgumentsInternal(Execution::Args& execArgs) const override; + void ExecuteInternal(Execution::Context& context) const override; + }; + struct FontListCommand final : public Command { FontListCommand(std::string_view parent) : Command("list", parent) {} diff --git a/src/AppInstallerCLICore/FontInstaller.cpp b/src/AppInstallerCLICore/FontInstaller.cpp new file mode 100644 index 0000000000..90a7d58033 --- /dev/null +++ b/src/AppInstallerCLICore/FontInstaller.cpp @@ -0,0 +1,25 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "ExecutionContext.h" +#include "FontInstaller.h" +#include +#include +#include +#include +#include + +namespace AppInstaller::CLI::Font +{ + FontInstaller::FontInstaller(const std::string& familyName, Manifest::ScopeEnum scope) + { + m_scope = scope; + m_familyName = familyName; + + // Get the expected state from the family name and scope. + } + + void FontInstaller::Install() + { + } +} diff --git a/src/AppInstallerCLICore/FontInstaller.h b/src/AppInstallerCLICore/FontInstaller.h new file mode 100644 index 0000000000..fb46ff19cb --- /dev/null +++ b/src/AppInstallerCLICore/FontInstaller.h @@ -0,0 +1,21 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include + +namespace AppInstaller::CLI::Font +{ + // Object representation of the metadata and functionality required for installing a font package. + struct FontInstaller + { + std::filesystem::path FontFileLocation; + + FontInstaller(const std::string& familyName, Manifest::ScopeEnum scope); + + void Install(); + + private: + Manifest::ScopeEnum m_scope; + std::string m_familyName; + }; +} diff --git a/src/AppInstallerCLICore/Resources.h b/src/AppInstallerCLICore/Resources.h index 0765d9f0d8..299aab16d4 100644 --- a/src/AppInstallerCLICore/Resources.h +++ b/src/AppInstallerCLICore/Resources.h @@ -256,6 +256,8 @@ namespace AppInstaller::CLI::Resource WINGET_DEFINE_RESOURCE_STRINGID(FontFamily); WINGET_DEFINE_RESOURCE_STRINGID(FontFamilyNameArgumentDescription); WINGET_DEFINE_RESOURCE_STRINGID(FontFilePaths); + WINGET_DEFINE_RESOURCE_STRINGID(FontInstallCommandLongDescription); + WINGET_DEFINE_RESOURCE_STRINGID(FontInstallCommandShortDescription); WINGET_DEFINE_RESOURCE_STRINGID(FontListCommandLongDescription); WINGET_DEFINE_RESOURCE_STRINGID(FontListCommandShortDescription); WINGET_DEFINE_RESOURCE_STRINGID(FontVersion); diff --git a/src/AppInstallerCLICore/Workflows/DownloadFlow.cpp b/src/AppInstallerCLICore/Workflows/DownloadFlow.cpp index 084cbb4d3f..456525e2ee 100644 --- a/src/AppInstallerCLICore/Workflows/DownloadFlow.cpp +++ b/src/AppInstallerCLICore/Workflows/DownloadFlow.cpp @@ -223,6 +223,7 @@ namespace AppInstaller::CLI::Workflow case InstallerTypeEnum::Portable: case InstallerTypeEnum::Wix: case InstallerTypeEnum::Zip: + case InstallerTypeEnum::Font: context << DownloadInstallerFile; break; case InstallerTypeEnum::Msix: diff --git a/src/AppInstallerCLICore/Workflows/FontFlow.cpp b/src/AppInstallerCLICore/Workflows/FontFlow.cpp index ce9bb7a233..f3defe3395 100644 --- a/src/AppInstallerCLICore/Workflows/FontFlow.cpp +++ b/src/AppInstallerCLICore/Workflows/FontFlow.cpp @@ -121,4 +121,8 @@ namespace AppInstaller::CLI::Workflow OutputInstalledFontFamiliesTable(context, lines); } } + + void FontInstallImpl(Execution::Context& context) + { + } } diff --git a/src/AppInstallerCLICore/Workflows/FontFlow.h b/src/AppInstallerCLICore/Workflows/FontFlow.h index c3346e1a60..ce98099b5a 100644 --- a/src/AppInstallerCLICore/Workflows/FontFlow.h +++ b/src/AppInstallerCLICore/Workflows/FontFlow.h @@ -10,4 +10,16 @@ namespace AppInstaller::CLI::Workflow // Inputs: None // Outputs: None void ReportInstalledFonts(Execution::Context& context); + + // Installs the font package. + // Required Args: None + // Inputs: Manifest, Scope, Rename, Location + // Outputs: None + void FontInstallImpl(Execution::Context& context); + + // Initializes the font installer. + // Required Args: None + // Inputs: Scope, Manifest, Installer + // Outputs: None + void InitializeFontInstaller(Execution::Context& context); } diff --git a/src/AppInstallerCLICore/Workflows/InstallFlow.cpp b/src/AppInstallerCLICore/Workflows/InstallFlow.cpp index e85540c379..ae633643cd 100644 --- a/src/AppInstallerCLICore/Workflows/InstallFlow.cpp +++ b/src/AppInstallerCLICore/Workflows/InstallFlow.cpp @@ -276,6 +276,18 @@ namespace AppInstaller::CLI::Workflow VerifyAndSetNestedInstaller << ExecuteInstallerForType(context.Get().value().NestedInstallerType); } + + // Runs the flow for installing a font package. + // Required Args: None + // Inputs: Installer, InstallerPath + // Outputs: None + void FontInstall(Execution::Context& context) + { + context << + InitializeFontInstaller << + FontInstallImpl << + ReportInstallerResult("Font"sv, APPINSTALLER_CLI_ERROR_FONT_INSTALL_FAILED, true); + } } bool ExemptFromSingleInstallLocking(InstallerTypeEnum type) @@ -443,6 +455,8 @@ namespace AppInstaller::CLI::Workflow case InstallerTypeEnum::Zip: context << details::ArchiveInstall; break; + case InstallerTypeEnum::Font: + context << details::FontInstall; default: THROW_HR(HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED)); } diff --git a/src/AppInstallerCLICore/Workflows/InstallFlow.h b/src/AppInstallerCLICore/Workflows/InstallFlow.h index cbc0ce70bc..30ac06dffe 100644 --- a/src/AppInstallerCLICore/Workflows/InstallFlow.h +++ b/src/AppInstallerCLICore/Workflows/InstallFlow.h @@ -49,6 +49,12 @@ namespace AppInstaller::CLI::Workflow // Inputs: Installer, InstallerPath, Manifest // Outputs: None void ArchiveInstall(Execution::Context& context); + + // Runs the flow for intsalling a font package. + // Required Args: None + // Inputs: Installer, InstallerPath, Manifest + // Outputs: None + void FontInstall(Execution::Context& context); } // Ensures that there is an applicable installer. diff --git a/src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw b/src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw index 7897bb985a..3f8f2c2f53 100644 --- a/src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw +++ b/src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw @@ -1812,7 +1812,7 @@ Please specify one of them using the --source option to proceed. Configuration successfully applied. - + Unit successfully applied. @@ -3147,7 +3147,7 @@ Please specify one of them using the --source option to proceed. Downloaded zero byte installer; ensure that your network connection is working properly. - + Manage fonts @@ -3183,4 +3183,10 @@ Please specify one of them using the --source option to proceed. Version - + + Install a font + + + Installs the selected font package, either found by searching a configured source or directly from a manifest. + + \ No newline at end of file diff --git a/src/AppInstallerCommonCore/Public/winget/ManifestCommon.h b/src/AppInstallerCommonCore/Public/winget/ManifestCommon.h index dc6b83bc2b..07e23db191 100644 --- a/src/AppInstallerCommonCore/Public/winget/ManifestCommon.h +++ b/src/AppInstallerCommonCore/Public/winget/ManifestCommon.h @@ -100,6 +100,7 @@ namespace AppInstaller::Manifest Burn, MSStore, Portable, + Font, }; enum class UpdateBehaviorEnum diff --git a/src/AppInstallerSharedLib/Public/AppInstallerErrors.h b/src/AppInstallerSharedLib/Public/AppInstallerErrors.h index 3524d812bf..69446aed11 100644 --- a/src/AppInstallerSharedLib/Public/AppInstallerErrors.h +++ b/src/AppInstallerSharedLib/Public/AppInstallerErrors.h @@ -152,6 +152,7 @@ #define APPINSTALLER_CLI_ERROR_SFSCLIENT_PACKAGE_NOT_SUPPORTED ((HRESULT)0x8A150084) #define APPINSTALLER_CLI_ERROR_LICENSING_API_FAILED_FORBIDDEN ((HRESULT)0x8A150085) #define APPINSTALLER_CLI_ERROR_INSTALLER_ZERO_BYTE_FILE ((HRESULT)0x8A150086) +#define APPINSTALLER_CLI_ERROR_FONT_INSTALL_FAILED ((HRESULT)0x8A150087) // Install errors. #define APPINSTALLER_CLI_ERROR_INSTALL_PACKAGE_IN_USE ((HRESULT)0x8A150101) diff --git a/src/Microsoft.Management.Deployment/PackageManager.idl b/src/Microsoft.Management.Deployment/PackageManager.idl index 943177fc45..2b3012328f 100644 --- a/src/Microsoft.Management.Deployment/PackageManager.idl +++ b/src/Microsoft.Management.Deployment/PackageManager.idl @@ -467,6 +467,8 @@ namespace Microsoft.Management.Deployment MSStore, /// Portable type. Portable, + /// Font type. + Font, }; /// The package installer scope. From 42e53bfbd28ba25719517879f3393dff30b247c3 Mon Sep 17 00:00:00 2001 From: --global Date: Mon, 2 Dec 2024 16:37:16 -0800 Subject: [PATCH 02/13] initial implementaiton for font installer --- src/AppInstallerCLICore/FontInstaller.cpp | 35 ++++++++++++--- src/AppInstallerCLICore/FontInstaller.h | 10 ++--- .../Workflows/DownloadFlow.cpp | 2 + .../Workflows/FontFlow.cpp | 36 +++++++++++++++ src/AppInstallerCLICore/Workflows/FontFlow.h | 6 --- .../Workflows/InstallFlow.cpp | 3 +- src/AppInstallerCommonCore/Fonts.cpp | 44 +++++++++++++++++-- .../Manifest/ManifestCommon.cpp | 9 +++- .../Public/winget/Fonts.h | 6 +++ 9 files changed, 129 insertions(+), 22 deletions(-) diff --git a/src/AppInstallerCLICore/FontInstaller.cpp b/src/AppInstallerCLICore/FontInstaller.cpp index 90a7d58033..e045e75ad0 100644 --- a/src/AppInstallerCLICore/FontInstaller.cpp +++ b/src/AppInstallerCLICore/FontInstaller.cpp @@ -11,15 +11,40 @@ namespace AppInstaller::CLI::Font { - FontInstaller::FontInstaller(const std::string& familyName, Manifest::ScopeEnum scope) + namespace { - m_scope = scope; - m_familyName = familyName; + constexpr std::wstring_view s_FontsPathSubkey = L"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Fonts"; + } + - // Get the expected state from the family name and scope. + FontInstaller::FontInstaller(Manifest::ScopeEnum scope) : m_scope(scope) + { + if (scope == Manifest::ScopeEnum::Machine) + { + m_installLocation = Runtime::GetPathTo(Runtime::PathName::FontsMachineInstallLocation); + m_key = Registry::Key::OpenIfExists(HKEY_LOCAL_MACHINE, std::wstring{ s_FontsPathSubkey }); + } + else + { + m_installLocation = Runtime::GetPathTo(Runtime::PathName::FontsUserInstallLocation); + m_key = Registry::Key::OpenIfExists(HKEY_CURRENT_USER, std::wstring{ s_FontsPathSubkey }); + } } - void FontInstaller::Install() + void FontInstaller::Install(const std::map fontFiles) { + // TODO: Get all font files and check if font file already exists. + for (const auto& [fontName, fontFilePath] : fontFiles) + { + std::filesystem::path fileName = fontFilePath.filename(); + std::filesystem::path fontFileInstallationPath = m_installLocation / fileName; + + AICLI_LOG(CLI, Info, << "Moving font file to: " << fontFileInstallationPath.u8string()); + AppInstaller::Filesystem::RenameFile(fontFilePath, fontFileInstallationPath); + + // TODO: Need to fix access permission for writing to the registry. + AICLI_LOG(CLI, Info, << "Creating font subkey for: " << AppInstaller::Utility::ConvertToUTF8(fontName)); + m_key.SetValue(fontName, fontFileInstallationPath, REG_SZ); + } } } diff --git a/src/AppInstallerCLICore/FontInstaller.h b/src/AppInstallerCLICore/FontInstaller.h index fb46ff19cb..1168185889 100644 --- a/src/AppInstallerCLICore/FontInstaller.h +++ b/src/AppInstallerCLICore/FontInstaller.h @@ -5,17 +5,17 @@ namespace AppInstaller::CLI::Font { - // Object representation of the metadata and functionality required for installing a font package. struct FontInstaller { - std::filesystem::path FontFileLocation; + FontInstaller(Manifest::ScopeEnum scope); - FontInstaller(const std::string& familyName, Manifest::ScopeEnum scope); + std::filesystem::path FontFileLocation; - void Install(); + void Install(const std::map fontFiles); private: Manifest::ScopeEnum m_scope; - std::string m_familyName; + std::filesystem::path m_installLocation; + Registry::Key m_key; }; } diff --git a/src/AppInstallerCLICore/Workflows/DownloadFlow.cpp b/src/AppInstallerCLICore/Workflows/DownloadFlow.cpp index 456525e2ee..cf483626ed 100644 --- a/src/AppInstallerCLICore/Workflows/DownloadFlow.cpp +++ b/src/AppInstallerCLICore/Workflows/DownloadFlow.cpp @@ -57,6 +57,8 @@ namespace AppInstaller::CLI::Workflow return L".msix"sv; case InstallerTypeEnum::Zip: return L".zip"sv; + case InstallerTypeEnum::Font: + return L".ttf"sv; default: THROW_HR(HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED)); } diff --git a/src/AppInstallerCLICore/Workflows/FontFlow.cpp b/src/AppInstallerCLICore/Workflows/FontFlow.cpp index f3defe3395..1d6c7ee8c1 100644 --- a/src/AppInstallerCLICore/Workflows/FontFlow.cpp +++ b/src/AppInstallerCLICore/Workflows/FontFlow.cpp @@ -5,10 +5,12 @@ #include "TableOutput.h" #include #include +#include namespace AppInstaller::CLI::Workflow { using namespace AppInstaller::CLI::Execution; + using namespace AppInstaller::CLI::Font; namespace { @@ -124,5 +126,39 @@ namespace AppInstaller::CLI::Workflow void FontInstallImpl(Execution::Context& context) { + Manifest::ScopeEnum scope = AppInstaller::Manifest::ConvertToScopeEnum(context.Args.GetArg(Execution::Args::Type::InstallScope)); + FontInstaller fontInstaller = FontInstaller(scope); + std::filesystem::path& installerPath = context.Get(); + std::map fontFileMap{}; + + try + { + context.Reporter.Info() << Resource::String::InstallFlowStartingPackageInstall << std::endl; + + // InstallerPath will point to a directory if it is extracted from an archive. + if (std::filesystem::is_directory(installerPath)) + { + const std::vector& nestedInstallerFiles = context.Get()->NestedInstallerFiles; + + for (const auto& nestedInstallerFile : nestedInstallerFiles) + { + const std::filesystem::path& fontFilePath = installerPath / ConvertToUTF16(nestedInstallerFile.RelativeFilePath); + auto fontFileEntryName = AppInstaller::Fonts::GetFontFileTitle(fontFilePath); + fontFileMap.emplace(fontFileEntryName, fontFilePath); + } + } + else + { + auto fontName = AppInstaller::Fonts::GetFontFileTitle(installerPath); + fontFileMap.emplace(fontName, installerPath); + } + + fontInstaller.Install(fontFileMap); + context.Add(ERROR_SUCCESS); + } + catch (...) + { + context.Add(Workflow::HandleException(context, std::current_exception())); + } } } diff --git a/src/AppInstallerCLICore/Workflows/FontFlow.h b/src/AppInstallerCLICore/Workflows/FontFlow.h index ce98099b5a..5ad5e0a2e8 100644 --- a/src/AppInstallerCLICore/Workflows/FontFlow.h +++ b/src/AppInstallerCLICore/Workflows/FontFlow.h @@ -16,10 +16,4 @@ namespace AppInstaller::CLI::Workflow // Inputs: Manifest, Scope, Rename, Location // Outputs: None void FontInstallImpl(Execution::Context& context); - - // Initializes the font installer. - // Required Args: None - // Inputs: Scope, Manifest, Installer - // Outputs: None - void InitializeFontInstaller(Execution::Context& context); } diff --git a/src/AppInstallerCLICore/Workflows/InstallFlow.cpp b/src/AppInstallerCLICore/Workflows/InstallFlow.cpp index ae633643cd..dcd299340e 100644 --- a/src/AppInstallerCLICore/Workflows/InstallFlow.cpp +++ b/src/AppInstallerCLICore/Workflows/InstallFlow.cpp @@ -3,6 +3,7 @@ #include "pch.h" #include "InstallFlow.h" #include "DownloadFlow.h" +#include "FontFlow.h" #include "UninstallFlow.h" #include "UpdateFlow.h" #include "ResumeFlow.h" @@ -284,7 +285,6 @@ namespace AppInstaller::CLI::Workflow void FontInstall(Execution::Context& context) { context << - InitializeFontInstaller << FontInstallImpl << ReportInstallerResult("Font"sv, APPINSTALLER_CLI_ERROR_FONT_INSTALL_FAILED, true); } @@ -457,6 +457,7 @@ namespace AppInstaller::CLI::Workflow break; case InstallerTypeEnum::Font: context << details::FontInstall; + break; default: THROW_HR(HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED)); } diff --git a/src/AppInstallerCommonCore/Fonts.cpp b/src/AppInstallerCommonCore/Fonts.cpp index 6f5f328f93..08625d1623 100644 --- a/src/AppInstallerCommonCore/Fonts.cpp +++ b/src/AppInstallerCommonCore/Fonts.cpp @@ -4,6 +4,8 @@ #include #include #include +#include +#include namespace AppInstaller::Fonts { @@ -49,15 +51,13 @@ namespace AppInstaller::Fonts FontCatalog::FontCatalog() { m_preferredLocales = AppInstaller::Locale::GetUserPreferredLanguagesUTF16(); + THROW_IF_FAILED(DWriteCreateFactory(DWRITE_FACTORY_TYPE_SHARED, __uuidof(m_factory), m_factory.put_unknown())); } std::vector FontCatalog::GetInstalledFontFamilies(std::optional familyName) { - wil::com_ptr factory; - THROW_IF_FAILED(DWriteCreateFactory(DWRITE_FACTORY_TYPE_SHARED, __uuidof(factory), factory.put_unknown())); - wil::com_ptr collection; - THROW_IF_FAILED(factory->GetSystemFontCollection(collection.addressof(), FALSE)); + THROW_IF_FAILED(m_factory->GetSystemFontCollection(collection.addressof(), FALSE)); std::vector installedFontFamilies; @@ -85,6 +85,20 @@ namespace AppInstaller::Fonts return installedFontFamilies; } + bool FontCatalog::IsValidFontFile(const std::filesystem::path& filePath) + { + wil::com_ptr fontFile; + THROW_IF_FAILED(m_factory->CreateFontFileReference(filePath.c_str(), NULL, &fontFile)); + + BOOL isValid; + DWRITE_FONT_FILE_TYPE fileType; + DWRITE_FONT_FACE_TYPE faceType; + UINT32 numOfFaces; + THROW_IF_FAILED(fontFile->Analyze(&isValid, &fileType, &faceType, &numOfFaces)); + + return isValid; + } + std::wstring FontCatalog::GetLocalizedStringFromFont(const wil::com_ptr& localizedStringCollection) { UINT32 index = 0; @@ -172,4 +186,26 @@ namespace AppInstaller::Fonts fontFamily.Faces = std::move(fontFaces); return fontFamily; } + + std::wstring GetFontFileTitle(const std::filesystem::path& fontFilePath) + { + auto co_unitialize = wil::CoInitializeEx(); + IPropertyStore* pps = nullptr; + std::wstring title; + HRESULT hr = SHGetPropertyStoreFromParsingName(fontFilePath.c_str(), nullptr, GPS_DEFAULT, IID_PPV_ARGS(&pps)); + if (SUCCEEDED(hr)) { + PROPVARIANT prop; + PropVariantInit(&prop); + hr = pps->GetValue(PKEY_Title, &prop); + if (SUCCEEDED(hr)) { + title = prop.pwszVal; + PropVariantClear(&prop); + pps->Release(); + } + PropVariantClear(&prop); + pps->Release(); + } + + return title; + } } diff --git a/src/AppInstallerCommonCore/Manifest/ManifestCommon.cpp b/src/AppInstallerCommonCore/Manifest/ManifestCommon.cpp index 16e759441c..491d165722 100644 --- a/src/AppInstallerCommonCore/Manifest/ManifestCommon.cpp +++ b/src/AppInstallerCommonCore/Manifest/ManifestCommon.cpp @@ -162,6 +162,10 @@ namespace AppInstaller::Manifest { result = InstallerTypeEnum::Portable; } + else if (inStrLower == "font") + { + result = InstallerTypeEnum::Font; + } return result; } @@ -583,6 +587,8 @@ namespace AppInstaller::Manifest return "msstore"sv; case InstallerTypeEnum::Portable: return "portable"sv; + case InstallerTypeEnum::Font: + return "font"sv; } return "unknown"sv; @@ -962,7 +968,8 @@ namespace AppInstaller::Manifest nestedInstallerType == InstallerTypeEnum::Wix || nestedInstallerType == InstallerTypeEnum::Burn || nestedInstallerType == InstallerTypeEnum::Portable || - nestedInstallerType == InstallerTypeEnum::Msix + nestedInstallerType == InstallerTypeEnum::Msix || + nestedInstallerType == InstallerTypeEnum::Font ); } diff --git a/src/AppInstallerCommonCore/Public/winget/Fonts.h b/src/AppInstallerCommonCore/Public/winget/Fonts.h index 443dea5f7a..396c36a58e 100644 --- a/src/AppInstallerCommonCore/Public/winget/Fonts.h +++ b/src/AppInstallerCommonCore/Public/winget/Fonts.h @@ -20,6 +20,8 @@ namespace AppInstaller::Fonts std::vector Faces; }; + std::wstring GetFontFileTitle(const std::filesystem::path& fontFilePath); + struct FontCatalog { FontCatalog(); @@ -27,6 +29,9 @@ namespace AppInstaller::Fonts // Gets all installed font families on the system. If an exact family name is provided and found, returns the installed font family. std::vector GetInstalledFontFamilies(std::optional familyName = {}); + // Returns a boolean value indicating whether the specified file path is a valid font file. + bool IsValidFontFile(const std::filesystem::path& filePath); + private: FontFamily GetFontFamilyByIndex(const wil::com_ptr& collection, UINT32 index); std::wstring GetLocalizedStringFromFont(const wil::com_ptr& localizedStringCollection); @@ -34,6 +39,7 @@ namespace AppInstaller::Fonts std::wstring GetFontFaceName(const wil::com_ptr& font); Utility::OpenTypeFontVersion GetFontFaceVersion(const wil::com_ptr& font); + wil::com_ptr m_factory; std::vector m_preferredLocales; }; } From 4efa09f4a1e93d99ef23710a10611d89d0424d33 Mon Sep 17 00:00:00 2001 From: --global Date: Thu, 5 Dec 2024 12:45:22 -0800 Subject: [PATCH 03/13] add tests --- .../Commands/FontCommand.cpp | 2 +- src/AppInstallerCLICore/FontInstaller.cpp | 55 ++++++++++++---- src/AppInstallerCLICore/FontInstaller.h | 14 +++- src/AppInstallerCLICore/Resources.h | 1 + .../Workflows/FontFlow.cpp | 46 ++++++++----- .../AppInstallerCLIE2ETests.csproj | 2 + src/AppInstallerCLIE2ETests/Constants.cs | 7 ++ src/AppInstallerCLIE2ETests/FontCommand.cs | 61 ++++++++++++++++++ .../Helpers/TestCommon.cs | 60 ++++++++++++++++- .../Helpers/TestIndex.cs | 24 ++++++- .../Helpers/TestSetup.cs | 6 ++ src/AppInstallerCLIE2ETests/Test.runsettings | 6 +- .../TestData/AppInstallerTestFont.ttf | Bin 0 -> 229376 bytes .../TestData/Manifests/TestFont.yaml | 14 ++++ .../TestData/Manifests/TestInvalidFont.yaml | 14 ++++ .../TestData/localsource.json | 6 ++ .../Shared/Strings/en-us/winget.resw | 3 + .../AppInstallerCLITests.vcxproj | 3 + .../AppInstallerCLITests.vcxproj.filters | 3 + src/AppInstallerCLITests/Fonts.cpp | 24 +++++++ .../TestData/TestFont.ttf | Bin 0 -> 229376 bytes src/AppInstallerCommonCore/Fonts.cpp | 34 ++++------ .../Manifest/ManifestCommon.cpp | 2 + .../Public/winget/Fonts.h | 2 +- .../Public/AppInstallerErrors.h | 1 + src/LocalhostWebServer/Startup.cs | 1 + .../Model/InstallerType.cs | 3 +- 27 files changed, 335 insertions(+), 59 deletions(-) create mode 100644 src/AppInstallerCLIE2ETests/FontCommand.cs create mode 100644 src/AppInstallerCLIE2ETests/TestData/AppInstallerTestFont.ttf create mode 100644 src/AppInstallerCLIE2ETests/TestData/Manifests/TestFont.yaml create mode 100644 src/AppInstallerCLIE2ETests/TestData/Manifests/TestInvalidFont.yaml create mode 100644 src/AppInstallerCLITests/TestData/TestFont.ttf diff --git a/src/AppInstallerCLICore/Commands/FontCommand.cpp b/src/AppInstallerCLICore/Commands/FontCommand.cpp index 2804e7aabf..454b7c194e 100644 --- a/src/AppInstallerCLICore/Commands/FontCommand.cpp +++ b/src/AppInstallerCLICore/Commands/FontCommand.cpp @@ -21,6 +21,7 @@ namespace AppInstaller::CLI { return InitializeFromMoveOnly>>({ std::make_unique(FullName()), + std::make_unique(FullName()), }); } @@ -49,7 +50,6 @@ namespace AppInstaller::CLI return { Argument::ForType(Args::Type::Manifest), Argument{ Args::Type::InstallScope, Resource::String::InstallScopeDescription, ArgumentType::Standard, Argument::Visibility::Help }, - Argument::ForType(Args::Type::Force), }; } diff --git a/src/AppInstallerCLICore/FontInstaller.cpp b/src/AppInstallerCLICore/FontInstaller.cpp index e045e75ad0..de6d35cc63 100644 --- a/src/AppInstallerCLICore/FontInstaller.cpp +++ b/src/AppInstallerCLICore/FontInstaller.cpp @@ -3,6 +3,7 @@ #include "pch.h" #include "ExecutionContext.h" #include "FontInstaller.h" +#include #include #include #include @@ -14,37 +15,65 @@ namespace AppInstaller::CLI::Font namespace { constexpr std::wstring_view s_FontsPathSubkey = L"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Fonts"; - } + constexpr std::wstring_view s_TrueType = L" (TrueType)"; + bool IsTrueTypeFont(DWRITE_FONT_FILE_TYPE fileType) + { + return ( + fileType == DWRITE_FONT_FILE_TYPE_TRUETYPE || + fileType == DWRITE_FONT_FILE_TYPE_TRUETYPE_COLLECTION + ); + } + } FontInstaller::FontInstaller(Manifest::ScopeEnum scope) : m_scope(scope) { if (scope == Manifest::ScopeEnum::Machine) { m_installLocation = Runtime::GetPathTo(Runtime::PathName::FontsMachineInstallLocation); - m_key = Registry::Key::OpenIfExists(HKEY_LOCAL_MACHINE, std::wstring{ s_FontsPathSubkey }); + m_key = Registry::Key::OpenIfExists(HKEY_LOCAL_MACHINE, std::wstring{ s_FontsPathSubkey }, 0, KEY_WRITE ); } else { m_installLocation = Runtime::GetPathTo(Runtime::PathName::FontsUserInstallLocation); - m_key = Registry::Key::OpenIfExists(HKEY_CURRENT_USER, std::wstring{ s_FontsPathSubkey }); + m_key = Registry::Key::OpenIfExists(HKEY_CURRENT_USER, std::wstring{ s_FontsPathSubkey }, 0, KEY_WRITE); } } - void FontInstaller::Install(const std::map fontFiles) + void FontInstaller::Install(const std::vector& fontFiles) { - // TODO: Get all font files and check if font file already exists. - for (const auto& [fontName, fontFilePath] : fontFiles) + for (const auto& fontFile : fontFiles) { - std::filesystem::path fileName = fontFilePath.filename(); - std::filesystem::path fontFileInstallationPath = m_installLocation / fileName; + const auto& filePath = fontFile.FilePath; + const auto& fileName = filePath.filename(); + const auto& destinationPath = m_installLocation / fileName; + + AICLI_LOG(CLI, Info, << "Getting Font title"); - AICLI_LOG(CLI, Info, << "Moving font file to: " << fontFileInstallationPath.u8string()); - AppInstaller::Filesystem::RenameFile(fontFilePath, fontFileInstallationPath); + std::wstring title = AppInstaller::Fonts::GetFontFileTitle(filePath); - // TODO: Need to fix access permission for writing to the registry. - AICLI_LOG(CLI, Info, << "Creating font subkey for: " << AppInstaller::Utility::ConvertToUTF8(fontName)); - m_key.SetValue(fontName, fontFileInstallationPath, REG_SZ); + if (IsTrueTypeFont(fontFile.FileType)) + { + title += s_TrueType; + } + + AICLI_LOG(CLI, Info, << "Moving font file to: " << destinationPath); + AppInstaller::Filesystem::RenameFile(filePath, destinationPath); + + AICLI_LOG(CLI, Info, << "Creating font subkey with name: " << AppInstaller::Utility::ConvertToUTF8(title)); + if (m_scope == Manifest::ScopeEnum::Machine) + { + m_key.SetValue(title, fileName, REG_SZ); + } + else + { + m_key.SetValue(title, destinationPath, REG_SZ); + } } } + + void FontInstaller::Uninstall(const std::wstring& familyName) + { + UNREFERENCED_PARAMETER(familyName); + } } diff --git a/src/AppInstallerCLICore/FontInstaller.h b/src/AppInstallerCLICore/FontInstaller.h index 1168185889..9f548e0908 100644 --- a/src/AppInstallerCLICore/FontInstaller.h +++ b/src/AppInstallerCLICore/FontInstaller.h @@ -2,16 +2,28 @@ // Licensed under the MIT License. #pragma once #include +#include namespace AppInstaller::CLI::Font { + struct FontFile + { + FontFile(std::filesystem::path filePath, DWRITE_FONT_FILE_TYPE fileType) + : FilePath(filePath), FileType(fileType) {} + + std::filesystem::path FilePath; + DWRITE_FONT_FILE_TYPE FileType; + }; + struct FontInstaller { FontInstaller(Manifest::ScopeEnum scope); std::filesystem::path FontFileLocation; - void Install(const std::map fontFiles); + void Install(const std::vector& fontFiles); + + void Uninstall(const std::wstring& familyName); private: Manifest::ScopeEnum m_scope; diff --git a/src/AppInstallerCLICore/Resources.h b/src/AppInstallerCLICore/Resources.h index 299aab16d4..335751e5e9 100644 --- a/src/AppInstallerCLICore/Resources.h +++ b/src/AppInstallerCLICore/Resources.h @@ -255,6 +255,7 @@ namespace AppInstaller::CLI::Resource WINGET_DEFINE_RESOURCE_STRINGID(FontFaces); WINGET_DEFINE_RESOURCE_STRINGID(FontFamily); WINGET_DEFINE_RESOURCE_STRINGID(FontFamilyNameArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(FontFileNotSupported); WINGET_DEFINE_RESOURCE_STRINGID(FontFilePaths); WINGET_DEFINE_RESOURCE_STRINGID(FontInstallCommandLongDescription); WINGET_DEFINE_RESOURCE_STRINGID(FontInstallCommandShortDescription); diff --git a/src/AppInstallerCLICore/Workflows/FontFlow.cpp b/src/AppInstallerCLICore/Workflows/FontFlow.cpp index 1d6c7ee8c1..1d1a1c12b5 100644 --- a/src/AppInstallerCLICore/Workflows/FontFlow.cpp +++ b/src/AppInstallerCLICore/Workflows/FontFlow.cpp @@ -126,34 +126,50 @@ namespace AppInstaller::CLI::Workflow void FontInstallImpl(Execution::Context& context) { - Manifest::ScopeEnum scope = AppInstaller::Manifest::ConvertToScopeEnum(context.Args.GetArg(Execution::Args::Type::InstallScope)); - FontInstaller fontInstaller = FontInstaller(scope); - std::filesystem::path& installerPath = context.Get(); - std::map fontFileMap{}; - try { - context.Reporter.Info() << Resource::String::InstallFlowStartingPackageInstall << std::endl; + const auto& installerPath = context.Get(); + std::vector filePaths; - // InstallerPath will point to a directory if it is extracted from an archive. + // InstallerPath will point to a directory if extracted from an archive. if (std::filesystem::is_directory(installerPath)) { const std::vector& nestedInstallerFiles = context.Get()->NestedInstallerFiles; - for (const auto& nestedInstallerFile : nestedInstallerFiles) - { - const std::filesystem::path& fontFilePath = installerPath / ConvertToUTF16(nestedInstallerFile.RelativeFilePath); - auto fontFileEntryName = AppInstaller::Fonts::GetFontFileTitle(fontFilePath); - fontFileMap.emplace(fontFileEntryName, fontFilePath); + { + filePaths.emplace_back(installerPath / ConvertToUTF16(nestedInstallerFile.RelativeFilePath)); } } else { - auto fontName = AppInstaller::Fonts::GetFontFileTitle(installerPath); - fontFileMap.emplace(fontName, installerPath); + filePaths.emplace_back(installerPath); } - fontInstaller.Install(fontFileMap); + std::vector fontFiles; + Fonts::FontCatalog fontCatalog; + + for (const auto& file : filePaths) + { + DWRITE_FONT_FILE_TYPE fileType; + if (!fontCatalog.IsFontFileSupported(file, fileType)) + { + AICLI_LOG(CLI, Warning, << "Font file is not supported: " << file); + context.Reporter.Error() << Resource::String::FontFileNotSupported << std::endl; + AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_FONT_FILE_NOT_SUPPORTED); + } + else + { + AICLI_LOG(CLI, Warning, << "Font file is supported: " << file); + fontFiles.emplace_back(FontFile(file, fileType)); + } + } + + context.Reporter.Info() << Resource::String::InstallFlowStartingPackageInstall << std::endl; + + Manifest::ScopeEnum scope = AppInstaller::Manifest::ConvertToScopeEnum(context.Args.GetArg(Execution::Args::Type::InstallScope)); + FontInstaller fontInstaller = FontInstaller(scope); + + fontInstaller.Install(fontFiles); context.Add(ERROR_SUCCESS); } catch (...) diff --git a/src/AppInstallerCLIE2ETests/AppInstallerCLIE2ETests.csproj b/src/AppInstallerCLIE2ETests/AppInstallerCLIE2ETests.csproj index 179ffb1583..5806dd9080 100644 --- a/src/AppInstallerCLIE2ETests/AppInstallerCLIE2ETests.csproj +++ b/src/AppInstallerCLIE2ETests/AppInstallerCLIE2ETests.csproj @@ -61,6 +61,8 @@ + + diff --git a/src/AppInstallerCLIE2ETests/Constants.cs b/src/AppInstallerCLIE2ETests/Constants.cs index 25fcb6d72c..f121fd5103 100644 --- a/src/AppInstallerCLIE2ETests/Constants.cs +++ b/src/AppInstallerCLIE2ETests/Constants.cs @@ -27,6 +27,7 @@ public class Constants public const string MsiInstallerPathParameter = "MsiTestInstallerPath"; public const string MsiInstallerV2PathParameter = "MsiTestInstallerV2Path"; public const string MsixInstallerPathParameter = "MsixTestInstallerPath"; + public const string FontPathParameter = "FontTestPath"; public const string PackageCertificatePathParameter = "PackageCertificatePath"; public const string PowerShellModulePathParameter = "PowerShellModulePath"; public const string SkipTestSourceParameter = "SkipTestSource"; @@ -55,11 +56,13 @@ public class Constants public const string MsiInstaller = "AppInstallerTestMsiInstaller"; public const string MsixInstaller = "AppInstallerTestMsixInstaller"; public const string ZipInstaller = "AppInstallerTestZipInstaller"; + public const string Font = "AppInstallerTestFont"; public const string ExeInstallerFileName = "AppInstallerTestExeInstaller.exe"; public const string MsiInstallerFileName = "AppInstallerTestMsiInstaller.msi"; public const string MsiInstallerV2FileName = "AppInstallerTestMsiInstallerV2.msi"; public const string MsixInstallerFileName = "AppInstallerTestMsixInstaller.msix"; public const string ZipInstallerFileName = "AppInstallerTestZipInstaller.zip"; + public const string FontFileName = "AppInstallerTestFont.ttf"; public const string ModifyRepairInstaller = "AppInstallerTest.TestModifyRepair"; public const string IndexPackage = "source.msix"; public const string MakeAppx = "makeappx.exe"; @@ -118,6 +121,8 @@ public class Constants public const string UninstallSubKey = @"Software\Microsoft\Windows\CurrentVersion\Uninstall"; public const string PathSubKey_User = @"Environment"; public const string PathSubKey_Machine = @"SYSTEM\CurrentControlSet\Control\Session Manager\Environment"; + public const string FontsSubKey = @"Software\Microsoft\Windows NT\CurrentVersion\Fonts"; + public const string TestFontSubKeyName = "Cascadia Code PL (True Type)"; // User settings public const string ArchiveExtractionMethod = "archiveExtractionMethod"; @@ -271,6 +276,8 @@ public class ErrorCode public const int ERROR_ADMIN_CONTEXT_REPAIR_PROHIBITED = unchecked((int)0x8A15007D); public const int ERROR_INSTALLER_ZERO_BYTE_FILE = unchecked((int)0x8A150086); + public const int ERROR_FONT_INSTALL_FAILED = unchecked((int)0x8A150087); + public const int ERROR_FONT_FILE_NOT_SUPPORTED = unchecked((int)0x8A150088); public const int ERROR_INSTALL_PACKAGE_IN_USE = unchecked((int)0x8A150101); public const int ERROR_INSTALL_INSTALL_IN_PROGRESS = unchecked((int)0x8A150102); diff --git a/src/AppInstallerCLIE2ETests/FontCommand.cs b/src/AppInstallerCLIE2ETests/FontCommand.cs new file mode 100644 index 0000000000..e32b05b727 --- /dev/null +++ b/src/AppInstallerCLIE2ETests/FontCommand.cs @@ -0,0 +1,61 @@ +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace AppInstallerCLIE2ETests +{ + using AppInstallerCLIE2ETests.Helpers; + using NUnit.Framework; + + /// + /// Test font command. + /// + public class FontCommand : BaseCommand + { + /// + /// One time set up. + /// + [SetUp] + public void OneTimeSetup() + { + WinGetSettingsHelper.ConfigureFeature("fonts", true); + } + + /// + /// Test install a font with user scope. + /// + [Test] + public void InstallFont() + { + var result = TestCommon.RunAICLICommand("font install", $"-m {TestCommon.GetTestDataFile(@"Manifests\TestFont.yaml")}"); + Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); + Assert.True(result.StdOut.Contains("Successfully installed")); + TestCommon.VerifyFontPackage(Constants.TestFontSubKeyName, Constants.FontFileName); + } + + /// + /// Test install a font with machine scope. + /// + [Test] + public void InstallFont_MachineScope() + { + var result = TestCommon.RunAICLICommand("font install", $"-m {TestCommon.GetTestDataFile(@"Manifests\TestFont.yaml")} --scope Machine"); + Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); + Assert.True(result.StdOut.Contains("Successfully installed")); + TestCommon.VerifyFontPackage(Constants.TestFontSubKeyName, Constants.FontFileName, TestCommon.Scope.Machine); + } + + /// + /// Test install an invalid font file. + /// + [Test] + public void InstallInvalidFont() + { + var result = TestCommon.RunAICLICommand("font install", $"-m {TestCommon.GetTestDataFile(@"Manifests\TestInvalidFont.yaml")} --scope Machine"); + Assert.AreEqual(Constants.ErrorCode.ERROR_FONT_FILE_NOT_SUPPORTED, result.ExitCode); + Assert.True(result.StdOut.Contains("The font file is not supported and cannot be installed.")); + } + } +} diff --git a/src/AppInstallerCLIE2ETests/Helpers/TestCommon.cs b/src/AppInstallerCLIE2ETests/Helpers/TestCommon.cs index 88fb61d5c0..658b022baf 100644 --- a/src/AppInstallerCLIE2ETests/Helpers/TestCommon.cs +++ b/src/AppInstallerCLIE2ETests/Helpers/TestCommon.cs @@ -344,6 +344,64 @@ public static string GetCheckpointsDirectory() } } + /// + /// Gets the fonts directory based on scope. + /// + /// Scope. + /// The path of the fonts directory. + public static string GetFontsDirectory(Scope scope) + { + if (scope == Scope.Machine) + { + return Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Windows), "Fonts"); + } + else + { + return Path.Combine(Environment.GetEnvironmentVariable("LocalAppData"), "Microsoft", "Windows", "Fonts"); + } + } + + /// + /// Verify font package. + /// + /// Name of the font registry subkey entry. + /// Filename of the installed font file. + /// Scope. + /// Should exist. + public static void VerifyFontPackage( + string fontSubKeyName, + string fontFileName, + Scope scope = Scope.User, + bool shouldExist = true) + { + // TODO: Update this function to be able to handle the font uninstall scenario. + string expectedFontInstallPath = Path.Combine(GetFontsDirectory(scope), fontFileName); + bool fontFileExists = File.Exists(expectedFontInstallPath); + + bool fontEntryExists; + RegistryKey baseKey = scope == Scope.Machine ? Registry.LocalMachine : Registry.CurrentUser; + using (RegistryKey fontsRegistryKey = baseKey.OpenSubKey(Constants.FontsSubKey, true)) + { + RegistryKey fontEntry = fontsRegistryKey.OpenSubKey(fontSubKeyName, true); + fontEntryExists = fontEntry != null; + } + + if (shouldExist) + { + // TODO: Replace with font uninstall when implemented. + File.Delete(expectedFontInstallPath); + + using (RegistryKey fontsRegistryKey = baseKey.OpenSubKey(Constants.FontsSubKey, true)) + { + fontsRegistryKey.DeleteSubKey(fontSubKeyName); + RegistryKey fontEntry = fontsRegistryKey.OpenSubKey(fontSubKeyName, true); + } + } + + Assert.AreEqual(shouldExist, fontFileExists, $"Expected font path: {expectedFontInstallPath}"); + Assert.AreEqual(shouldExist, fontEntryExists, $"Expected {fontSubKeyName} subkey in registry path: {Constants.FontsSubKey}"); + } + /// /// Verify portable package. /// @@ -351,7 +409,7 @@ public static string GetCheckpointsDirectory() /// Command alias. /// File name. /// Product code. - /// Should exists. + /// Should exist. /// Scope. /// Install directory added to path instead of the symlink directory. public static void VerifyPortablePackage( diff --git a/src/AppInstallerCLIE2ETests/Helpers/TestIndex.cs b/src/AppInstallerCLIE2ETests/Helpers/TestIndex.cs index 2728661e86..d2eb625e7a 100644 --- a/src/AppInstallerCLIE2ETests/Helpers/TestIndex.cs +++ b/src/AppInstallerCLIE2ETests/Helpers/TestIndex.cs @@ -8,7 +8,6 @@ namespace AppInstallerCLIE2ETests.Helpers { using System; using System.IO; - using System.Text.Json; using Microsoft.WinGetSourceCreator; using WinGetSourceCreator.Model; @@ -25,6 +24,7 @@ static TestIndex() TestIndex.MsiInstallerV2 = Path.Combine(TestSetup.Parameters.StaticFileRootPath, Constants.MsiInstaller, Constants.MsiInstallerV2FileName); TestIndex.MsixInstaller = Path.Combine(TestSetup.Parameters.StaticFileRootPath, Constants.MsixInstaller, Constants.MsixInstallerFileName); TestIndex.ZipInstaller = Path.Combine(TestSetup.Parameters.StaticFileRootPath, Constants.ZipInstaller, Constants.ZipInstallerFileName); + TestIndex.Font = Path.Combine(TestSetup.Parameters.StaticFileRootPath, Constants.Font, Constants.FontFileName); } /// @@ -52,6 +52,11 @@ static TestIndex() /// public static string ZipInstaller { get; private set; } + /// + /// Gets the font file path used by the manifests in the E2E test. + /// + public static string Font { get; private set; } + /// /// Generate test source. /// @@ -99,6 +104,16 @@ public static void GenerateE2ESource() throw new FileNotFoundException(testParams.MsixInstallerPath); } + if (string.IsNullOrEmpty(testParams.FontPath)) + { + throw new ArgumentNullException($"{Constants.FontPathParameter} is required"); + } + + if (!File.Exists(testParams.FontPath)) + { + throw new FileNotFoundException(testParams.FontPath); + } + if (string.IsNullOrEmpty(testParams.PackageCertificatePath)) { throw new ArgumentNullException($"{Constants.PackageCertificatePathParameter} is required"); @@ -148,6 +163,13 @@ public static void GenerateE2ESource() HashToken = "", SignatureToken = "", }, + new LocalInstaller + { + Type = InstallerType.Font, + Name = Path.Combine(Constants.Font, Constants.FontFileName), + Input = testParams.FontPath, + HashToken = "", + }, }, DynamicInstallers = new () { diff --git a/src/AppInstallerCLIE2ETests/Helpers/TestSetup.cs b/src/AppInstallerCLIE2ETests/Helpers/TestSetup.cs index b681fc710f..73faa5afcd 100644 --- a/src/AppInstallerCLIE2ETests/Helpers/TestSetup.cs +++ b/src/AppInstallerCLIE2ETests/Helpers/TestSetup.cs @@ -44,6 +44,7 @@ private TestSetup() this.MsiInstallerPath = this.InitializeFileParam(Constants.MsiInstallerPathParameter); this.MsixInstallerPath = this.InitializeFileParam(Constants.MsixInstallerPathParameter); this.MsiInstallerV2Path = this.InitializeFileParam(Constants.MsiInstallerV2PathParameter); + this.MsiInstallerV2Path = this.InitializeFileParam(Constants.FontPathParameter); this.ForcedExperimentalFeatures = this.InitializeStringArrayParam(Constants.ForcedExperimentalFeaturesParameter); } @@ -119,6 +120,11 @@ public static TestSetup Parameters /// public string ZipInstallerPath { get; } + /// + /// Gets the font path. + /// + public string FontPath { get; } + /// /// Gets the package cert path. /// diff --git a/src/AppInstallerCLIE2ETests/Test.runsettings b/src/AppInstallerCLIE2ETests/Test.runsettings index 6f6079e24e..6c4742a3f4 100644 --- a/src/AppInstallerCLIE2ETests/Test.runsettings +++ b/src/AppInstallerCLIE2ETests/Test.runsettings @@ -1,4 +1,4 @@ - +