Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Font Install #5042

Open
wants to merge 14 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .github/actions/spelling/expect.txt
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,7 @@ FOF
foldc
foldcase
FOLDERID
FONTHASH
FORPARSING
foundfr
fsanitize
Expand Down Expand Up @@ -427,6 +428,7 @@ pid
pidl
pidlist
PKCS
PKEY
pkgmgr
pkindex
pkix
Expand All @@ -440,7 +442,10 @@ PRIMARYKEY
processthreads
productcode
PRODUCTICON
propkey
PROPVARIANT
proxystub
pwsz
pscustomobject
pseudocode
PSHOST
Expand Down
4 changes: 3 additions & 1 deletion doc/windows/package-manager/winget/returnCodes.md
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,8 @@ ms.localizationpriority: medium
| 0x8A150084 | -1978335100 | APPINSTALLER_CLI_ERROR_SFSCLIENT_PACKAGE_NOT_SUPPORTED | The Microsoft Store package does not support download command. |
| 0x8A150085 | -1978335099 | APPINSTALLER_CLI_ERROR_LICENSING_API_FAILED_FORBIDDEN | Failed to retrieve Microsoft Store package license. The Microsoft Entra Id account does not have required privilege. |
| 0x8A150086 | -1978335098 | APPINSTALLER_CLI_ERROR_INSTALLER_ZERO_BYTE_FILE | Downloaded zero byte installer; ensure that your network connection is working properly. |
| 0x8A150087 | -1978335097 | APPINSTALLER_CLI_ERROR_FONT_INSTALL_FAILED | Failed to install font package. |
| 0x8A150088 | -1978335096 | APPINSTALLER_CLI_ERROR_FONT_FILE_NOT_SUPPORTED | Font file is not supported and cannot be installed. |

## Install errors.

Expand All @@ -170,7 +172,7 @@ ms.localizationpriority: medium
| 0x8A150111 | -1978334959 | APPINSTALLER_CLI_ERROR_INSTALL_PACKAGE_IN_USE_BY_APPLICATION | Application is currently in use by another application. |
| 0x8A150112 | -1978334958 | APPINSTALLER_CLI_ERROR_INSTALL_INVALID_PARAMETER | Invalid parameter. |
| 0x8A150113 | -1978334957 | APPINSTALLER_CLI_ERROR_INSTALL_SYSTEM_NOT_SUPPORTED | Package not supported by the system. |
| 0x8A150114 | -1978334956 | APPINSTALLER_CLI_ERROR_INSTALL_UPGRADE_NOT_SUPPORTED | The installer does not support upgrading an existing package. |
| 0x8A150114 | -1978334956 | APPINSTALLER_CLI_ERROR_INSTALL_UPGRADE_NOT_SUPPORTED | The installer does not support upgrading an existing package. |
| 0x8A150115 | -1978334955 | APPINSTALLER_CLI_ERROR_INSTALL_CUSTOM_ERROR | Installation failed with installer custom error. |

## Check for package installed status
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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"
},
Expand All @@ -80,7 +81,8 @@
"nullsoft",
"wix",
"burn",
"portable"
"portable",
"font"
],
"description": "Enumeration of supported nested installer types contained inside an archive file"
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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"
},
Expand All @@ -182,7 +183,8 @@
"nullsoft",
"wix",
"burn",
"portable"
"portable",
"font"
],
"description": "Enumeration of supported nested installer types contained inside an archive file"
},
Expand Down
2 changes: 2 additions & 0 deletions src/AppInstallerCLICore/AppInstallerCLICore.vcxproj
Original file line number Diff line number Diff line change
Expand Up @@ -398,6 +398,7 @@
<ClInclude Include="ExecutionContext.h" />
<ClInclude Include="ExecutionProgress.h" />
<ClInclude Include="ExecutionReporter.h" />
<ClInclude Include="FontInstaller.h" />
<ClInclude Include="Invocation.h" />
<ClInclude Include="pch.h" />
<ClInclude Include="PortableInstaller.h" />
Expand Down Expand Up @@ -483,6 +484,7 @@
<ClCompile Include="ExecutionContext.cpp" />
<ClCompile Include="ExecutionProgress.cpp" />
<ClCompile Include="ExecutionReporter.cpp" />
<ClCompile Include="FontInstaller.cpp" />
<ClCompile Include="pch.cpp">
<PrecompiledHeader>Create</PrecompiledHeader>
</ClCompile>
Expand Down
6 changes: 6 additions & 0 deletions src/AppInstallerCLICore/AppInstallerCLICore.vcxproj.filters
Original file line number Diff line number Diff line change
Expand Up @@ -266,6 +266,9 @@
<ClInclude Include="Workflows\FontFlow.h">
<Filter>Workflows</Filter>
</ClInclude>
<ClInclude Include="FontInstaller.h">
<Filter>Header Files</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<ClCompile Include="pch.cpp">
Expand Down Expand Up @@ -502,6 +505,9 @@
<ClCompile Include="Workflows\FontFlow.cpp">
<Filter>Workflows</Filter>
</ClCompile>
<ClCompile Include="FontInstaller.cpp">
<Filter>Source Files</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<None Include="PropertySheet.props" />
Expand Down
48 changes: 48 additions & 0 deletions src/AppInstallerCLICore/Commands/FontCommand.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -20,6 +21,7 @@ namespace AppInstaller::CLI
{
return InitializeFromMoveOnly<std::vector<std::unique_ptr<Command>>>({
std::make_unique<FontListCommand>(FullName()),
std::make_unique<FontInstallCommand>(FullName()),
});
}

Expand All @@ -43,6 +45,52 @@ namespace AppInstaller::CLI
OutputHelp(context.Reporter);
}

std::vector<Argument> FontInstallCommand::GetArguments() const
{
return {
Argument::ForType(Args::Type::Manifest),
Argument{ Args::Type::InstallScope, Resource::String::InstallScopeDescription, ArgumentType::Standard, Argument::Visibility::Help },
};
}

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;
ryfu-msft marked this conversation as resolved.
Show resolved Hide resolved
}

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<Argument> FontListCommand::GetArguments() const
{
return {
Expand Down
18 changes: 18 additions & 0 deletions src/AppInstallerCLICore/Commands/FontCommand.h
Original file line number Diff line number Diff line change
Expand Up @@ -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<Argument> 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) {}
Expand Down
144 changes: 144 additions & 0 deletions src/AppInstallerCLICore/FontInstaller.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
#include "pch.h"
#include "ExecutionContext.h"
#include "FontInstaller.h"
#include <winget/Fonts.h>
#include <winget/Manifest.h>
#include <winget/ManifestCommon.h>
#include <winget/Filesystem.h>
#include <AppInstallerErrors.h>
#include <AppInstallerRuntime.h>

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
);
}
}

FontFile::FontFile(std::filesystem::path filePath, DWRITE_FONT_FILE_TYPE fileType)
: FilePath(std::move(filePath)), FileType(fileType)
{
Title = AppInstaller::Fonts::GetFontFileTitle(FilePath);

if (IsTrueTypeFont(FileType))
{
Title += s_TrueType;
}
}

FontInstaller::FontInstaller(Manifest::ScopeEnum scope) : m_scope(scope)
{
if (scope == Manifest::ScopeEnum::Machine)
{
m_installLocation = Runtime::GetPathTo(Runtime::PathName::FontsMachineInstallLocation);
m_key = Registry::Key::Create(HKEY_LOCAL_MACHINE, std::wstring{ s_FontsPathSubkey });
}
else
{
m_installLocation = Runtime::GetPathTo(Runtime::PathName::FontsUserInstallLocation);
m_key = Registry::Key::Create(HKEY_CURRENT_USER, std::wstring{ s_FontsPathSubkey });
}
}

bool FontInstaller::EnsureInstall()
{
for (auto& fontFile : m_fontFiles)
{
if (m_key[fontFile.Title].has_value())
{
if (!std::filesystem::exists(m_key[fontFile.Title]->GetValue<Registry::Value::Type::String>()))
Copy link
Member

Choose a reason for hiding this comment

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

Retrieve m_key[fontFile.Title] once rather than in both conditions.

{
AICLI_LOG(CLI, Info, << "Removing existing font value as font file does not exist.");
m_key.DeleteValue(fontFile.Title);
}
else
{
AICLI_LOG(CLI, Info, << "Existing font value found: " << AppInstaller::Utility::ConvertToUTF8(fontFile.Title));
return false;
}
}

std::filesystem::path destinationPath = m_installLocation / fontFile.FilePath.filename();
auto initialStem = fontFile.FilePath.stem();
auto extension = fontFile.FilePath.extension();

// If a file exists at the destination path, make the filename unique.
int index = 0;
while (std::filesystem::exists(destinationPath))
{
std::filesystem::path unique = { "_" + std::to_string(index) };
auto duplicateStem = initialStem;
duplicateStem += unique;
duplicateStem += extension;
destinationPath = m_installLocation / duplicateStem;
index++;
}

fontFile.DestinationPath = std::move(destinationPath);
}

return true;
}

void FontInstaller::Install()
{
bool isMachineScope = m_scope == Manifest::ScopeEnum::Machine;

for (const auto& fontFile : m_fontFiles)
{
AICLI_LOG(CLI, Info, << "Creating font value with name : " << AppInstaller::Utility::ConvertToUTF8(fontFile.Title));
if (isMachineScope)
{
m_key.SetValue(fontFile.Title, fontFile.DestinationPath.filename(), REG_SZ);
}
else
{
m_key.SetValue(fontFile.Title, fontFile.DestinationPath, REG_SZ);
}
}

for (const auto& fontFile : m_fontFiles)
{
AICLI_LOG(CLI, Info, << "Moving font file to: " << fontFile.DestinationPath);
AppInstaller::Filesystem::RenameFile(fontFile.FilePath, fontFile.DestinationPath);
}
}

void FontInstaller::Uninstall()
{
for (const auto& fontFile : m_fontFiles)
{
if (m_key[fontFile.Title].has_value())
{
AICLI_LOG(CLI, Info, << "Existing font value found:" << AppInstaller::Utility::ConvertToUTF8(fontFile.Title));
std::filesystem::path existingFontFilePath = { m_key[fontFile.Title]->GetValue<Registry::Value::Type::String>() };
Copy link
Member

Choose a reason for hiding this comment

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

You will want to assign the path from a wide string; I think there is a registry value to get that directly.


if (m_scope == Manifest::ScopeEnum::Machine)
{
// Font entries in the HKEY_LOCAL_MACHINE hive only have the filename specified as the value. Prepend install location.
existingFontFilePath = m_installLocation / existingFontFilePath;
}

if (std::filesystem::exists(existingFontFilePath))
{
AICLI_LOG(CLI, Info, << "Removing existing font file at:" << existingFontFilePath);
std::filesystem::remove(existingFontFilePath);
Copy link
Member

Choose a reason for hiding this comment

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

If the operation fails, we cannot revert this. Much like my comment on the rest of the operation being reverted on failure, we want some mechanism to put things back the way they were.

I would think that to make it maximally recoverable, one would need to:

  1. Create a temporary portable tracking database for the file and registry changes (probably requires some amount of addition to the tracked things)
  2. Set up a resume that can leverage the database to revert to the previous state

But I don't think we need to be that extreme (handling full on process termination). A more modest approach would have a vector of IOperationSteps that you populate as you go. On exiting the operation, you either invoke Complete or Revert on all of the items, depending on success or failure. In this case, Complete deletes the renamed file, while Revert renames it back to its old state.

}

AICLI_LOG(CLI, Info, << "Deleting registry value:" << existingFontFilePath);
m_key.DeleteValue(fontFile.Title);
}
}
}
}
Loading
Loading