Skip to content
Draft
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
1 change: 1 addition & 0 deletions .github/actions/spelling/expect.txt
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,7 @@ FOF
FOLDERID
FONTCHANGE
FONTHASH
fontlayout
FORPARSING
foundfr
fsanitize
Expand Down
25 changes: 25 additions & 0 deletions src/AppInstallerCLICore/Workflows/FontFlow.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -284,6 +284,31 @@ namespace AppInstaller::CLI::Workflow
}
}

void FontRepairImpl(Execution::Context& context)
{
const auto& installedPackage = context.Get<Execution::Data::InstalledPackageVersion>();
Fonts::FontContext fontContext;
fontContext.InstallerSource = InstallerSource::WinGet;
fontContext.Scope = Manifest::ConvertToScopeEnum(installedPackage->GetMetadata()[Repository::PackageVersionMetadata::InstalledScope]);
fontContext.PackageId = ConvertToUTF16(installedPackage->GetProperty(Repository::PackageVersionProperty::Moniker));
fontContext.PackageVersion = ConvertToUTF16(installedPackage->GetProperty(Repository::PackageVersionProperty::Version));

if (fontContext.Scope == Manifest::ScopeEnum::User && Runtime::IsRunningWithNonDefaultFullToken())

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Whichever merges second will need to update this: #6634

Depending on the specifics, it might already be covered by the general repair flow blocker but I don't remember right now.

{
context.Reporter.Error() << Resource::String::NoAdminRepairForUserScopePackage << std::endl;
AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_ADMIN_CONTEXT_ACTION_PROHIBITED);
}
if (fontContext.Scope == Manifest::ScopeEnum::Machine && !Runtime::IsRunningAsAdmin())
{
context.Reporter.Error() << Resource::String::CommandRequiresAdmin << std::endl;
AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_COMMAND_REQUIRES_ADMIN);
}

context.Reporter.Info() << Resource::String::RepairFlowStartingPackageRepair << std::endl;
const auto result = Fonts::RepairFontPackage(fontContext);
context.Add<Execution::Data::OperationReturnCode>(result.HResult);
}

void FontUninstallImpl(Execution::Context& context)
{
context.Reporter.Info() << Resource::String::UninstallFlowStartingPackageUninstall << std::endl;
Expand Down
6 changes: 6 additions & 0 deletions src/AppInstallerCLICore/Workflows/FontFlow.h
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,12 @@ namespace AppInstaller::CLI::Workflow
// Outputs: None
void FontInstallImpl(Execution::Context& context);

// Repairs the installed font package's files and registrations.
// Required Args: None
// Inputs: InstalledPackageVersion
// Outputs: OperationReturnCode
void FontRepairImpl(Execution::Context& context);

// Uninstalls the font package.
// Required Args: None
// Inputs: Manifest, Scope, Rename, Location
Expand Down
15 changes: 15 additions & 0 deletions src/AppInstallerCLICore/Workflows/RepairFlow.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
#include "Workflows/DownloadFlow.h"
#include "Workflows/ArchiveFlow.h"
#include "Workflows/InstallFlow.h"
#include "Workflows/FontFlow.h"
#include "Workflows/PromptFlow.h"
#include "winget/ManifestCommon.h"
#include "AppInstallerDeployment.h"
Expand Down Expand Up @@ -242,6 +243,7 @@ namespace AppInstaller::CLI::Workflow
switch (installerTypeEnum)
{
case InstallerTypeEnum::Msi:
case InstallerTypeEnum::Font:
return false;
case InstallerTypeEnum::Msix:
// For MSIX packages that are from the Microsoft Store, selecting an installer is required.
Expand Down Expand Up @@ -362,6 +364,13 @@ namespace AppInstaller::CLI::Workflow
MSStoreRepair;
}
break;
case InstallerTypeEnum::Font:
{
context <<
FontRepairImpl <<
ReportRepairResult("Font", APPINSTALLER_CLI_ERROR_EXEC_REPAIR_FAILED, true);
}
break;
case InstallerTypeEnum::Portable:
default:
THROW_HR(HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED));
Expand Down Expand Up @@ -400,6 +409,12 @@ namespace AppInstaller::CLI::Workflow
}
break;
case InstallerTypeEnum::MSStore:
case InstallerTypeEnum::Font:
break;
case InstallerTypeEnum::Zip:
THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED),
!context.Contains(Execution::Data::Installer) ||
context.Get<Execution::Data::Installer>()->EffectiveInstallerType() != InstallerTypeEnum::Font);
break;
case InstallerTypeEnum::Portable:
default:
Expand Down
66 changes: 57 additions & 9 deletions src/AppInstallerCLIE2ETests/FontCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@

namespace AppInstallerCLIE2ETests
{
using System.IO;
using AppInstallerCLIE2ETests.Helpers;
using Microsoft.Win32;
using NUnit.Framework;

/// <summary>
Expand All @@ -31,14 +33,19 @@ public void InstallFont_UserScope()
{
var fontPackageName = "AppInstallerTest.TestFont";
var fontPackageVersion = "1.0.0.0";
var installResult = TestCommon.RunAICLICommand("install", fontPackageName);
var installResult = TestCommon.RunAICLICommand("install", $"{fontPackageName} --scope User");
Assert.That(installResult.ExitCode, Is.EqualTo(Constants.ErrorCode.S_OK));
Assert.That(installResult.StdOut, Does.Contain("Successfully installed"));
TestCommon.VerifyFontPackage(fontPackageName, fontPackageVersion, TestCommon.Scope.User);

var uninstallResult = TestCommon.RunAICLICommand("uninstall", fontPackageName);
Assert.That(uninstallResult.ExitCode, Is.EqualTo(Constants.ErrorCode.S_OK));
TestCommon.VerifyFontPackage(fontPackageName, fontPackageVersion, TestCommon.Scope.User, false);
try
{
Assert.That(installResult.StdOut, Does.Contain("Successfully installed"));
TestCommon.VerifyFontPackage(fontPackageName, fontPackageVersion, new[] { Constants.FontFileName }, TestCommon.Scope.User);
}
finally
{
var uninstallResult = TestCommon.RunAICLICommand("uninstall", $"{fontPackageName} --scope User");
Assert.That(uninstallResult.ExitCode, Is.EqualTo(Constants.ErrorCode.S_OK));
TestCommon.VerifyFontPackage(fontPackageName, fontPackageVersion, new[] { Constants.FontFileName }, TestCommon.Scope.User, false);
}
}

/// <summary>
Expand All @@ -52,11 +59,52 @@ public void InstallFont_MachineScope()
var result = TestCommon.RunAICLICommand("install", $"{fontPackageName} --scope Machine");
Assert.That(result.ExitCode, Is.EqualTo(Constants.ErrorCode.S_OK));
Assert.That(result.StdOut, Does.Contain("Successfully installed"));
TestCommon.VerifyFontPackage(fontPackageName, fontPackageVersion, TestCommon.Scope.Machine);
TestCommon.VerifyFontPackage(fontPackageName, fontPackageVersion, new[] { Constants.FontFileName }, TestCommon.Scope.Machine);

var uninstallResult = TestCommon.RunAICLICommand("uninstall", fontPackageName);
Assert.That(uninstallResult.ExitCode, Is.EqualTo(Constants.ErrorCode.S_OK));
TestCommon.VerifyFontPackage(fontPackageName, fontPackageVersion, TestCommon.Scope.Machine, false);
TestCommon.VerifyFontPackage(fontPackageName, fontPackageVersion, new[] { Constants.FontFileName }, TestCommon.Scope.Machine, false);
}

/// <summary>
/// Test repair of a user font's discovery registration.
/// </summary>
[Test]
public void RepairFont_UserScope()
{
var fontPackageName = "AppInstallerTest.TestFont";
var fontPackageVersion = "1.0.0.0";
var installResult = TestCommon.RunAICLICommand("install", $"{fontPackageName} --scope User");
Assert.That(installResult.ExitCode, Is.EqualTo(Constants.ErrorCode.S_OK));
try
{
using (var fontsKey = Registry.CurrentUser.OpenSubKey(Constants.FontsSubKey, true))
using (var packageKey = fontsKey.OpenSubKey(
$"Microsoft.DesktopAppInstaller_8wekyb3d8bbwe\\{fontPackageName}\\{fontPackageVersion}"))
{
Assert.That(packageKey, Is.Not.Null);
var names = packageKey.GetValueNames();
Assert.That(names, Is.Not.Empty);
foreach (var name in names)
{
var installedPath = packageKey.GetValue(name).ToString();
var registrationName = Path.GetFileName(installedPath);
Assert.That(fontsKey.GetValue(registrationName), Is.EqualTo(installedPath));
fontsKey.DeleteValue(registrationName);
}
}

var repairResult = TestCommon.RunAICLICommand("repair", $"{fontPackageName} --scope User");
Assert.That(repairResult.ExitCode, Is.EqualTo(Constants.ErrorCode.S_OK));
Assert.That(repairResult.StdOut, Does.Contain("Repair operation completed successfully."));
TestCommon.VerifyFontPackage(fontPackageName, fontPackageVersion, new[] { Constants.FontFileName }, TestCommon.Scope.User);
}
finally
{
var uninstallResult = TestCommon.RunAICLICommand("uninstall", $"{fontPackageName} --scope User");
Assert.That(uninstallResult.ExitCode, Is.EqualTo(Constants.ErrorCode.S_OK));
TestCommon.VerifyFontPackage(fontPackageName, fontPackageVersion, new[] { Constants.FontFileName }, TestCommon.Scope.User, false);
}
}

/// <summary>
Expand Down
65 changes: 32 additions & 33 deletions src/AppInstallerCLIE2ETests/Helpers/TestCommon.cs
Original file line number Diff line number Diff line change
Expand Up @@ -376,58 +376,57 @@ public static string GetFontsDirectory(Scope scope)
/// </summary>
/// <param name="packageName">Name of the package.</param>
/// <param name="packageVersion">Name of the package version.</param>
/// <param name="expectedFileNames">Original filenames of the package's fonts.</param>
/// <param name="scope">Scope.</param>
/// <param name="shouldExist">If package should exist.</param>
public static void VerifyFontPackage(
string packageName,
string packageVersion,
IEnumerable<string> expectedFileNames,
Scope scope = Scope.User,
bool shouldExist = true)
{
RegistryKey baseKey = (scope == Scope.Machine) ? Registry.LocalMachine : Registry.CurrentUser;

var fileList = new List<string>();
using (RegistryKey fontsRegistryKey = baseKey.OpenSubKey(Constants.FontsSubKey, true))
using (RegistryKey fontsRegistryKey = baseKey.OpenSubKey(Constants.FontsSubKey))
{
using var winGetRootKey = fontsRegistryKey.OpenSubKey("Microsoft.DesktopAppInstaller_8wekyb3d8bbwe");
if (shouldExist)
{
Assert.That(winGetRootKey, Is.Not.Null);
}
else
if (!shouldExist)
{
using var installedVersion = fontsRegistryKey?.OpenSubKey(
$"Microsoft.DesktopAppInstaller_8wekyb3d8bbwe\\{packageName}\\{packageVersion}");
Assert.That(installedVersion, Is.Null);
return;
}

using var packageNameSubkey = winGetRootKey.OpenSubKey(packageName);
if (shouldExist)
{
Assert.That(packageNameSubkey, Is.Not.Null);
}

if (packageNameSubkey is not null)
{
using var versionSubkey = packageNameSubkey.OpenSubKey(packageVersion);
Assert.That(fontsRegistryKey, Is.Not.Null);
using var winGetRootKey = fontsRegistryKey.OpenSubKey("Microsoft.DesktopAppInstaller_8wekyb3d8bbwe");
Assert.That(winGetRootKey, Is.Not.Null);

if (shouldExist)
{
Assert.That(versionSubkey, Is.Not.Null);
}
else
{
Assert.That(versionSubkey, Is.Null);
}
using var packageNameSubkey = winGetRootKey.OpenSubKey(packageName);
Assert.That(packageNameSubkey, Is.Not.Null);

if (versionSubkey is not null)
{
var valueNames = versionSubkey.GetValueNames();
foreach (var valueName in valueNames)
{
fileList.Add(versionSubkey.GetValue(valueName).ToString());
}
using var versionSubkey = packageNameSubkey.OpenSubKey(packageVersion);
Assert.That(versionSubkey, Is.Not.Null);

Assert.That(fileList.Count, Is.EqualTo(valueNames.Length));
}
Assert.That(expectedFileNames, Is.Not.Empty);
var valueNames = versionSubkey.GetValueNames();
Assert.That(valueNames, Is.EquivalentTo(expectedFileNames));
foreach (var valueName in valueNames)
{
Assert.That(versionSubkey.GetValueKind(valueName), Is.EqualTo(RegistryValueKind.String));
var installedPath = (string)versionSubkey.GetValue(valueName, null, RegistryValueOptions.DoNotExpandEnvironmentNames);
Assert.That(installedPath, Is.Not.Null.And.Not.Empty);
Assert.That(Path.IsPathFullyQualified(installedPath), Is.True);
Assert.That(Path.GetDirectoryName(installedPath), Is.EqualTo(GetFontsDirectory(scope)).IgnoreCase);
fileList.Add(installedPath);

var registrationName = Path.GetFileName(installedPath);
var expectedRegistration = scope == Scope.Machine ? registrationName : installedPath;
Assert.That(
fontsRegistryKey.GetValue(registrationName, null, RegistryValueOptions.DoNotExpandEnvironmentNames),
Is.EqualTo(expectedRegistration));
Assert.That(fontsRegistryKey.GetValueKind(registrationName), Is.EqualTo(RegistryValueKind.String));
}
}

Expand Down
Loading
Loading