Skip to content

Commit 855d227

Browse files
authored
Improved manifest validations for MSI and Windows Feature names (#6170)
CP of #6169 onto main branch. ## Change Adds some additional validation for MSI switches and Windows Feature names: 1. MSI switches are checked during full manifest validation in the same way that they are checked when we attempt to use the MSI APIs rather than msiexec. The API is used for fully all silent installs of MSIs, so we now ensure that those will work (6 total manifests in winget-pkgs were found that needed fixes). 2. Windows Feature names in dependencies are validated to be { alphanumeric, `-`, `_` }. This is done both during full manifest validation and at runtime. A PowerShell script (tools/ManifestValidation/Invoke-ManifestValidation.ps1) is added that will run `wingetdev validate` against a directory recursively and generate a report. There is support for resuming in the middle of the run. This would probably perform much better if it were updated to use the WinGetUtil API, but that can be a future project.
1 parent 5ae357b commit 855d227

28 files changed

Lines changed: 857 additions & 31 deletions

.github/actions/spelling/expect.txt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ ACCESSDENIED
77
ACCESSTOKEN
88
ACL'd
99
adjacents
10+
adminproperties
1011
adml
1112
admx
1213
AFAIK
@@ -98,6 +99,7 @@ COMGLB
9899
commandline
99100
compressapi
100101
concurrencysal
102+
Consolas
101103
constexpr
102104
contactsupport
103105
contentfiles
@@ -281,6 +283,7 @@ Kaido
281283
KNOWNFOLDERID
282284
kool
283285
ktf
286+
LASTEXITCODE
284287
LCID
285288
learnxinyminutes
286289
LEBOM
@@ -349,6 +352,7 @@ msdownload
349352
msft
350353
msftrubengu
351354
MSIHASH
355+
msinewinstance
352356
MSIXHASH
353357
MSIXSTRM
354358
msstore
@@ -513,6 +517,7 @@ sddl
513517
secureobject
514518
securestring
515519
seekp
520+
Segoe
516521
seof
517522
servercert
518523
servercertificate

src/AppInstallerCLICore/Workflows/DependenciesFlow.cpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,12 @@ namespace AppInstaller::CLI::Workflow
175175
{
176176
AICLI_LOG(Core, Info, << "Successfully enabled [" << featureName << "]");
177177
}
178+
else if (result == E_INVALIDARG)
179+
{
180+
AICLI_LOG(Core, Warning, << "Invalid Windows Feature name [" << featureName << "]");
181+
enableFeaturesFailed = true;
182+
featureContext.Reporter.Warn() << Resource::String::WindowsFeatureNotFound(locIndFeatureName) << std::endl;
183+
}
178184
else if (result == 0x800f080c) // DISMAPI_E_UNKNOWN_FEATURE
179185
{
180186
AICLI_LOG(Core, Warning, << "Windows Feature [" << featureName << "] does not exist");

src/AppInstallerCLICore/Workflows/InstallFlow.cpp

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -61,14 +61,8 @@ namespace AppInstaller::CLI::Workflow
6161

6262
bool ShouldUseDirectMSIInstall(InstallerTypeEnum type, bool isSilentInstall)
6363
{
64-
switch (type)
65-
{
66-
case InstallerTypeEnum::Msi:
67-
case InstallerTypeEnum::Wix:
68-
return isSilentInstall || ExperimentalFeature::IsEnabled(ExperimentalFeature::Feature::DirectMSI);
69-
default:
70-
return false;
71-
}
64+
return DoesInstallerTypeUseMsiProperties(type) &&
65+
(isSilentInstall || ExperimentalFeature::IsEnabled(ExperimentalFeature::Feature::DirectMSI));
7266
}
7367

7468
bool ShouldErrorForUnsupportedArgument(UnsupportedArgumentEnum arg)

src/AppInstallerCLICore/Workflows/ShellExecuteInstallerHandler.cpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -509,6 +509,12 @@ namespace AppInstaller::CLI::Workflow
509509

510510
void ShellExecuteEnableWindowsFeature::operator()(Execution::Context& context) const
511511
{
512+
if (!Utility::IsValidWindowsFeaturePattern(m_featureName))
513+
{
514+
context.Add<Execution::Data::OperationReturnCode>(static_cast<DWORD>(E_INVALIDARG));
515+
return;
516+
}
517+
512518
Utility::LocIndView locIndFeatureName{ m_featureName };
513519

514520
std::optional<DWORD> doesFeatureExistResult = DoesWindowsFeatureExist(context, m_featureName);

src/AppInstallerCLITests/AppInstallerCLITests.vcxproj

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1081,6 +1081,18 @@
10811081
<CopyFileToFolders Include="TestData\ManifestV1_28-PowerShellDSC.yaml">
10821082
<DeploymentContent>true</DeploymentContent>
10831083
</CopyFileToFolders>
1084+
<CopyFileToFolders Include="TestData\Manifest-Bad-InvalidWindowsFeatureName.yaml">
1085+
<DeploymentContent>true</DeploymentContent>
1086+
</CopyFileToFolders>
1087+
<CopyFileToFolders Include="TestData\Manifest-Bad-BlockedMsiProperty.yaml">
1088+
<DeploymentContent>true</DeploymentContent>
1089+
</CopyFileToFolders>
1090+
<CopyFileToFolders Include="TestData\Manifest-Bad-InvalidMsiSwitches.yaml">
1091+
<DeploymentContent>true</DeploymentContent>
1092+
</CopyFileToFolders>
1093+
<CopyFileToFolders Include="TestData\Manifest-Bad-NetworkAddressInSwitches.yaml">
1094+
<DeploymentContent>true</DeploymentContent>
1095+
</CopyFileToFolders>
10841096
</ItemGroup>
10851097
<ItemGroup>
10861098
<ProjectReference Include="..\AppInstallerCLICore\AppInstallerCLICore.vcxproj">

src/AppInstallerCLITests/AppInstallerCLITests.vcxproj.filters

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1152,5 +1152,17 @@
11521152
<CopyFileToFolders Include="TestData\ManifestV1_28-Singleton.yaml">
11531153
<Filter>TestData</Filter>
11541154
</CopyFileToFolders>
1155+
<CopyFileToFolders Include="TestData\Manifest-Bad-InvalidWindowsFeatureName.yaml">
1156+
<Filter>TestData</Filter>
1157+
</CopyFileToFolders>
1158+
<CopyFileToFolders Include="TestData\Manifest-Bad-BlockedMsiProperty.yaml">
1159+
<Filter>TestData</Filter>
1160+
</CopyFileToFolders>
1161+
<CopyFileToFolders Include="TestData\Manifest-Bad-InvalidMsiSwitches.yaml">
1162+
<Filter>TestData</Filter>
1163+
</CopyFileToFolders>
1164+
<CopyFileToFolders Include="TestData\Manifest-Bad-NetworkAddressInSwitches.yaml">
1165+
<Filter>TestData</Filter>
1166+
</CopyFileToFolders>
11551167
</ItemGroup>
11561168
</Project>

src/AppInstallerCLITests/InstallDependenciesFlow.cpp

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
#include "pch.h"
44
#include "WorkflowCommon.h"
55
#include "DependenciesTestSource.h"
6+
#include <AppInstallerRuntime.h>
67
#include <Commands/InstallCommand.h>
78
#include <Commands/COMCommand.h>
89
#include <Workflows/DependenciesFlow.h>
@@ -328,6 +329,42 @@ TEST_CASE("InstallFlow_Dependencies_COM", "[InstallFlow][workflow][dependencies]
328329
REQUIRE(installationOrder.at(2) == "AppInstallerCliTest.TestExeInstaller.MultipleDependencies");
329330
}
330331

332+
void InstallFlow_Dependencies_WindowsFeaturesArgument_Generic(std::string_view featureName)
333+
{
334+
std::ostringstream installOutput;
335+
TestContext context{ installOutput, std::cin };
336+
337+
context << ShellExecuteEnableWindowsFeature(featureName);
338+
339+
INFO(installOutput.str());
340+
341+
REQUIRE(context.Contains(Execution::Data::OperationReturnCode));
342+
REQUIRE(context.Get<Execution::Data::OperationReturnCode>() == E_INVALIDARG);
343+
}
344+
345+
TEST_CASE("InstallFlow_Dependencies_WindowsFeaturesArgument_Extras", "[InstallFlow][workflow][dependencies][111981]")
346+
{
347+
TempFile potentialLogFile("dism-log", ".log");
348+
std::string featureName = "MediaPlayback /LogPath:";
349+
featureName.append(potentialLogFile.GetPath().u8string());
350+
351+
InstallFlow_Dependencies_WindowsFeaturesArgument_Generic(featureName);
352+
353+
REQUIRE(!std::filesystem::exists(potentialLogFile));
354+
}
355+
356+
TEST_CASE("InstallFlow_Dependencies_WindowsFeaturesArgument_Quoted", "[InstallFlow][workflow][dependencies][111981]")
357+
{
358+
TempFile potentialLogFile("dism-log", ".log");
359+
std::string featureName = "\"MediaPlayback /LogPath:";
360+
featureName.append(potentialLogFile.GetPath().u8string());
361+
featureName.append("\"");
362+
363+
InstallFlow_Dependencies_WindowsFeaturesArgument_Generic(featureName);
364+
365+
REQUIRE(!std::filesystem::exists(potentialLogFile));
366+
}
367+
331368
// TODO:
332369
// add dependencies for installer tests to DependenciesTestSource (or a new one)
333370
// add tests for min version dependency solving

src/AppInstallerCLITests/Strings.cpp

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -354,3 +354,28 @@ TEST_CASE("ConvertControlCodesToPictures", "[strings]")
354354

355355
REQUIRE(ConvertControlCodesToPictures(allCodes) == ConvertToUTF8(allPictures));
356356
}
357+
358+
TEST_CASE("IsValidWindowsFeaturePattern_AllFound_True", "[strings][111981]")
359+
{
360+
for (const auto& name : {
361+
"IIS-ODBCLogging",
362+
"NetFx3",
363+
"SMB1Protocol",
364+
})
365+
{
366+
INFO(name);
367+
REQUIRE(IsValidWindowsFeaturePattern(name));
368+
}
369+
}
370+
371+
TEST_CASE("IsValidWindowsFeaturePattern_Bad_False", "[strings][111981]")
372+
{
373+
for (const auto& name : {
374+
"MediaPlayback /LogPath:C:\\file.txt",
375+
"\"MediaPlayback /LogPath:C:\\file.txt\"",
376+
})
377+
{
378+
INFO(name);
379+
REQUIRE(!IsValidWindowsFeaturePattern(name));
380+
}
381+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
# Installer with a blocked MSI property in a switch value
2+
# yaml-language-server: $schema=https://aka.ms/winget-manifest.singleton.1.0.0.schema.json
3+
4+
PackageIdentifier: AppInstallerCliTest.TestMsiInstaller
5+
PackageVersion: 1.0.0.0
6+
PackageLocale: en-US
7+
PackageName: AppInstaller Test MSI Installer
8+
ShortDescription: AppInstaller Test MSI Installer
9+
Publisher: Microsoft Corporation
10+
License: Test
11+
InstallerType: msi
12+
Installers:
13+
- Architecture: x64
14+
InstallerUrl: https://ThisIsNotUsed
15+
InstallerSha256: 6a2d3683fa19bf00e58e07d1313d20a5f5735ebbd6a999d33381d28740ee07ea
16+
InstallerSwitches:
17+
Silent: TRANSFORMS=evil.mst
18+
ManifestType: singleton
19+
ManifestVersion: 1.0.0
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
# Installer with unparseable MSI switch arguments
2+
# yaml-language-server: $schema=https://aka.ms/winget-manifest.singleton.1.0.0.schema.json
3+
4+
PackageIdentifier: AppInstallerCliTest.TestMsiInstaller
5+
PackageVersion: 1.0.0.0
6+
PackageLocale: en-US
7+
PackageName: AppInstaller Test MSI Installer
8+
ShortDescription: AppInstaller Test MSI Installer
9+
Publisher: Microsoft Corporation
10+
License: Test
11+
InstallerType: msi
12+
Installers:
13+
- Architecture: x64
14+
InstallerUrl: https://ThisIsNotUsed
15+
InstallerSha256: 6a2d3683fa19bf00e58e07d1313d20a5f5735ebbd6a999d33381d28740ee07ea
16+
InstallerSwitches:
17+
Silent: '@INVALID'
18+
ManifestType: singleton
19+
ManifestVersion: 1.0.0

0 commit comments

Comments
 (0)