Skip to content

Commit 5b5f289

Browse files
authored
✨ Detect .NET Framework as package (#5838)
* ✨ Detect .NET Framework as package .NET Framework bundled with Windows does not show up in the default package list. Query the registry keys separatly and add the disciovered version to the packages. Signed-off-by: Christian Zunker <christian@mondoo.com> * refactor create package Signed-off-by: Christian Zunker <christian@mondoo.com> --------- Signed-off-by: Christian Zunker <christian@mondoo.com>
1 parent 060dc05 commit 5b5f289

File tree

2 files changed

+226
-87
lines changed

2 files changed

+226
-87
lines changed

providers/os/resources/packages/windows_packages.go

Lines changed: 127 additions & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ import (
2525
"go.mondoo.com/cnquery/v11/providers/os/resources/cpe"
2626
"go.mondoo.com/cnquery/v11/providers/os/resources/powershell"
2727
"go.mondoo.com/cnquery/v11/providers/os/resources/purl"
28+
"go.mondoo.com/ranger-rpc/codes"
29+
"go.mondoo.com/ranger-rpc/status"
2830
)
2931

3032
// ProcessorArchitecture Enum
@@ -130,38 +132,9 @@ func (p winAppxPackages) toPackage(platform *inventory.Platform) Package {
130132
p.arch = arch
131133
}
132134

133-
pkg := Package{
134-
Name: p.Name,
135-
Version: p.Version,
136-
Arch: p.arch,
137-
Format: "windows/appx",
138-
Vendor: p.Publisher,
139-
PUrl: purl.NewPackageURL(platform, purl.TypeAppx, p.Name, p.Version).String(),
140-
}
141-
if p.InstallLocation != "" {
142-
pkg.Files = []FileRecord{
143-
{
144-
Path: p.InstallLocation,
145-
},
146-
}
147-
pkg.FilesAvailable = PkgFilesIncluded
148-
}
149-
150-
if p.Version != "" {
151-
cpeWfns, err := cpe.NewPackage2Cpe(p.Publisher, p.Name, p.Version, "", "")
152-
if err != nil {
153-
log.Debug().Err(err).
154-
Str("name", p.Name).
155-
Str("version", p.Version).
156-
Msg("could not create cpe for windows appx package")
157-
} else {
158-
pkg.CPEs = cpeWfns
159-
}
160-
} else {
161-
log.Debug().Msg("ignored package since information is missing")
162-
}
135+
pkg := createPackage(p.Name, p.Version, "windows/appx", p.arch, p.Publisher, p.InstallLocation, platform)
163136

164-
return pkg
137+
return *pkg
165138
}
166139

167140
// Good read: https://www.wintips.org/view-installed-apps-and-packages-in-windows-10-8-1-8-from-powershell/
@@ -278,9 +251,63 @@ func (w *WinPkgManager) getLocalInstalledApps() ([]Package, error) {
278251
packages = append(packages, *p)
279252
}
280253
}
254+
255+
// These are the .NET Framework packages
256+
// They do not show up in the general apps or features list, so we need to discover them separately
257+
dotNetFramework, err := w.getDotNetFramework()
258+
if err != nil {
259+
return nil, err
260+
}
261+
packages = append(packages, dotNetFramework...)
262+
281263
return packages, nil
282264
}
283265

266+
// getDotNetFramework returns the .NET Framework package
267+
func (w *WinPkgManager) getDotNetFramework() ([]Package, error) {
268+
// https://learn.microsoft.com/en-us/dotnet/framework/install/how-to-determine-which-versions-are-installed#net-framework-45-and-later-versions
269+
dotNet45plus := "HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\NET Framework Setup\\NDP\\v4\\Full"
270+
// https://learn.microsoft.com/en-us/dotnet/framework/install/how-to-determine-which-versions-are-installed#use-registry-editor-older-framework-versions
271+
dotNet35 := "HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\NET Framework Setup\\NDP\\v3.5"
272+
273+
return getDotNetFrameworkPackageFromRegistryKeys(dotNet45plus, dotNet35, w.platform)
274+
}
275+
276+
// getDotNetFrameworkFs returns the .NET Framework package discovered on the filesystem
277+
func (w *WinPkgManager) getDotNetFrameworkFs() ([]Package, error) {
278+
// https://learn.microsoft.com/en-us/dotnet/framework/install/how-to-determine-which-versions-are-installed#net-framework-45-and-later-versions
279+
dotNet45plus := "Microsoft\\NET Framework Setup\\NDP\\v4\\Full"
280+
// https://learn.microsoft.com/en-us/dotnet/framework/install/how-to-determine-which-versions-are-installed#use-registry-editor-older-framework-versions
281+
dotNet35 := "Microsoft\\NET Framework Setup\\NDP\\v3.5"
282+
283+
return getDotNetFrameworkPackageFromRegistryKeys(dotNet45plus, dotNet35, w.platform)
284+
}
285+
286+
// getDotNetFrameworkPackageFromRegistryKeys returns the .NET Framework package from the registry keys
287+
func getDotNetFrameworkPackageFromRegistryKeys(dotNet45plus, dotNet35 string, platform *inventory.Platform) ([]Package, error) {
288+
items, err := registry.GetNativeRegistryKeyItems(dotNet45plus)
289+
if err != nil && status.Code(err) != codes.NotFound {
290+
return nil, err
291+
}
292+
293+
if len(items) == 0 {
294+
items, err = registry.GetNativeRegistryKeyItems(dotNet35)
295+
if err != nil && status.Code(err) != codes.NotFound {
296+
return nil, err
297+
}
298+
}
299+
300+
if len(items) == 0 {
301+
return nil, nil
302+
}
303+
304+
p := getDotNetFrameworkPackageFromRegistryKeyItems(items, platform)
305+
if p == nil {
306+
return nil, nil
307+
}
308+
return []Package{*p}, nil
309+
}
310+
284311
func (w *WinPkgManager) getInstalledApps() ([]Package, error) {
285312
if w.conn.Type() == shared.Type_Local && runtime.GOOS == "windows" {
286313
return w.getLocalInstalledApps()
@@ -374,6 +401,14 @@ func (w *WinPkgManager) getFsInstalledApps() ([]Package, error) {
374401
}
375402
}
376403

404+
// These are the .NET Framework packages
405+
// They do not show up in the general apps or features list, so we need to discover them separately
406+
dotNetFramework, err := w.getDotNetFrameworkFs()
407+
if err != nil {
408+
return nil, err
409+
}
410+
packages = append(packages, dotNetFramework...)
411+
377412
msSqlHotfixes := findMsSqlHotfixes(packages)
378413
if len(msSqlHotfixes) > 0 {
379414
packages = updateMsSqlPackages(packages, msSqlHotfixes[len(msSqlHotfixes)-1])
@@ -490,35 +525,31 @@ func getPackageFromRegistryKeyItems(children []registry.RegistryKeyItem, platfor
490525
return nil
491526
}
492527

493-
pkg := &Package{
494-
Name: displayName,
495-
Version: displayVersion,
496-
Format: "windows/app",
497-
Arch: platform.Arch,
498-
Vendor: publisher,
499-
PUrl: purl.NewPackageURL(
500-
platform, purl.TypeWindows, displayName, displayVersion,
501-
).String(),
502-
}
503-
if installLocation != "" {
504-
pkg.Files = []FileRecord{
505-
{
506-
Path: installLocation,
507-
},
528+
pkg := createPackage(displayName, displayVersion, "windows/app", platform.Arch, publisher, installLocation, platform)
529+
530+
return pkg
531+
}
532+
533+
// getDotNetFrameworkPackageFromRegistryKeyItems returns the .NET Framework package from the registry key items
534+
func getDotNetFrameworkPackageFromRegistryKeyItems(items []registry.RegistryKeyItem, platform *inventory.Platform) *Package {
535+
var version string
536+
var installLocation string
537+
538+
for _, i := range items {
539+
switch i.Key {
540+
case "Version":
541+
version = i.Value.String
542+
case "InstallLocation":
543+
installLocation = i.Value.String
508544
}
509-
pkg.FilesAvailable = PkgFilesIncluded
510545
}
511546

512-
if displayVersion != "" {
513-
cpeWfns, err := cpe.NewPackage2Cpe(publisher, displayName, displayVersion, "", "")
514-
if err != nil {
515-
log.Debug().Err(err).Str("name", displayName).Str("version", displayVersion).Msg("could not create cpe for windows app package")
516-
} else {
517-
pkg.CPEs = cpeWfns
518-
}
519-
} else {
520-
log.Debug().Msg("ignored package since information is missing")
547+
if version == "" {
548+
return nil
521549
}
550+
551+
pkg := createPackage("Microsoft .NET Framework", version, "windows/app", platform.Arch, "Microsoft", installLocation, platform)
552+
522553
return pkg
523554
}
524555

@@ -596,44 +627,15 @@ func ParseWindowsAppPackages(platform *inventory.Platform, input io.Reader) ([]P
596627
if entry.UninstallString == "" {
597628
continue
598629
}
599-
cpeWfns := []string{}
600630

601631
// TODO: We need to figure out why we have empty displayNames.
602632
// this is common in windows but we need to verify it is a windows
603633
// issue and not a cnquery issue.
604634
if entry.DisplayName == "" {
605635
continue
606636
}
607-
if entry.DisplayVersion != "" {
608-
cpeWfns, err = cpe.NewPackage2Cpe(entry.Publisher, entry.DisplayName, entry.DisplayVersion, "", "")
609-
if err != nil {
610-
log.Debug().Err(err).
611-
Str("name", entry.DisplayName).
612-
Str("version", entry.DisplayVersion).
613-
Msg("could not create cpe for windows app package")
614-
}
615-
} else {
616-
log.Debug().Msg("ignored package since information is missing")
617-
}
618-
pkg := Package{
619-
Name: entry.DisplayName,
620-
Version: entry.DisplayVersion,
621-
Format: "windows/app",
622-
CPEs: cpeWfns,
623-
Vendor: entry.Publisher,
624-
Arch: platform.Arch,
625-
PUrl: purl.NewPackageURL(
626-
platform, purl.TypeWindows, entry.DisplayName, entry.DisplayVersion,
627-
).String(),
628-
}
629-
if entry.InstallLocation != "" {
630-
pkg.Files = []FileRecord{
631-
{
632-
Path: entry.InstallLocation,
633-
},
634-
}
635-
}
636-
pkgs = append(pkgs, pkg)
637+
pkg := createPackage(entry.DisplayName, entry.DisplayVersion, "windows/app", platform.Arch, entry.Publisher, entry.InstallLocation, platform)
638+
pkgs = append(pkgs, *pkg)
637639
}
638640

639641
return pkgs, nil
@@ -682,3 +684,41 @@ func updateMsSqlPackages(pkgs []Package, latestMsSqlHotfix Package) []Package {
682684
}
683685
return pkgs
684686
}
687+
688+
// createPackage creates a new package with the given parameters
689+
func createPackage(name, version, format, arch, publisher, installLocation string, platform *inventory.Platform) *Package {
690+
purlType := purl.TypeWindows
691+
if format == "windows/appx" {
692+
purlType = purl.TypeAppx
693+
}
694+
695+
pkg := &Package{
696+
Name: name,
697+
Version: version,
698+
Format: format,
699+
Arch: arch,
700+
Vendor: publisher,
701+
PUrl: purl.NewPackageURL(
702+
platform, purlType, name, version,
703+
).String(),
704+
}
705+
if installLocation != "" {
706+
pkg.Files = []FileRecord{
707+
{
708+
Path: installLocation,
709+
},
710+
}
711+
pkg.FilesAvailable = PkgFilesIncluded
712+
}
713+
714+
if version != "" {
715+
cpeWfns, err := cpe.NewPackage2Cpe(publisher, name, version, "", "")
716+
if err != nil {
717+
log.Debug().Err(err).Str("name", name).Str("version", version).Msg("could not create cpe for windows app package")
718+
} else {
719+
pkg.CPEs = cpeWfns
720+
}
721+
}
722+
723+
return pkg
724+
}

providers/os/resources/packages/windows_packages_test.go

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -338,3 +338,102 @@ func TestFindAndUpdateMsSqlHotfixes(t *testing.T) {
338338
require.Equal(t, "1.0.0", pkg.Version, "expected non-SQL Server package to remain unchanged")
339339
assert.Equal(t, "pkg:windows/windows/Not%20a%20hotfix@1.0.0?arch=x86", pkg.PUrl)
340340
}
341+
342+
func TestGetDotNetFrameworkPackageFromRegistryKeyItems(t *testing.T) {
343+
platform := &inventory.Platform{
344+
Name: "windows",
345+
Version: "19041",
346+
Arch: "x86_64",
347+
Family: []string{"windows"},
348+
}
349+
350+
t.Run("with version and install location", func(t *testing.T) {
351+
// Create mock registry items for .NET Framework 4.8
352+
items := []registry.RegistryKeyItem{
353+
{
354+
Key: "Version",
355+
Value: registry.RegistryKeyValue{
356+
Kind: registry.SZ,
357+
String: "4.8.04084.0",
358+
},
359+
},
360+
{
361+
Key: "InstallLocation",
362+
Value: registry.RegistryKeyValue{
363+
Kind: registry.SZ,
364+
String: "C:\\Windows\\Microsoft.NET\\Framework64\\v4.8.04084\\",
365+
},
366+
},
367+
}
368+
369+
pkg := getDotNetFrameworkPackageFromRegistryKeyItems(items, platform)
370+
require.NotNil(t, pkg, "expected package to be created")
371+
372+
assert.Equal(t, "Microsoft .NET Framework", pkg.Name)
373+
assert.Equal(t, "4.8.04084.0", pkg.Version)
374+
assert.Equal(t, "windows/app", pkg.Format)
375+
assert.Equal(t, "x86_64", pkg.Arch)
376+
assert.Equal(t, "Microsoft", pkg.Vendor)
377+
assert.Equal(t, "pkg:windows/windows/Microsoft%20.NET%20Framework@4.8.04084.0?arch=x86_64", pkg.PUrl)
378+
assert.Len(t, pkg.Files, 1)
379+
assert.Equal(t, "C:\\Windows\\Microsoft.NET\\Framework64\\v4.8.04084\\", pkg.Files[0].Path)
380+
assert.Equal(t, PkgFilesIncluded, pkg.FilesAvailable)
381+
382+
// Verify CPE generation
383+
require.Len(t, pkg.CPEs, 2, "expected 2 CPE entries")
384+
expectedCPEs := []string{
385+
"cpe:2.3:a:microsoft:microsoft_.net_framework:4.8.04084.0:*:*:*:*:*:*:*",
386+
"cpe:2.3:a:microsoft:microsoft_.net_framework:4.8.04084:*:*:*:*:*:*:*",
387+
}
388+
assert.ElementsMatch(t, expectedCPEs, pkg.CPEs)
389+
})
390+
391+
t.Run("with .NET 3.5 version", func(t *testing.T) {
392+
// Create mock registry items for .NET Framework 3.5
393+
items := []registry.RegistryKeyItem{
394+
{
395+
Key: "Version",
396+
Value: registry.RegistryKeyValue{
397+
Kind: registry.SZ,
398+
String: "3.5.30729.4926",
399+
},
400+
},
401+
{
402+
Key: "InstallLocation",
403+
Value: registry.RegistryKeyValue{
404+
Kind: registry.SZ,
405+
String: "C:\\Windows\\Microsoft.NET\\Framework\\v3.5\\",
406+
},
407+
},
408+
}
409+
410+
pkg := getDotNetFrameworkPackageFromRegistryKeyItems(items, platform)
411+
require.NotNil(t, pkg, "expected package to be created")
412+
413+
assert.Equal(t, "Microsoft .NET Framework", pkg.Name)
414+
assert.Equal(t, "3.5.30729.4926", pkg.Version)
415+
assert.Equal(t, "windows/app", pkg.Format)
416+
assert.Equal(t, "x86_64", pkg.Arch)
417+
assert.Equal(t, "Microsoft", pkg.Vendor)
418+
assert.Equal(t, "pkg:windows/windows/Microsoft%20.NET%20Framework@3.5.30729.4926?arch=x86_64", pkg.PUrl)
419+
assert.Len(t, pkg.Files, 1)
420+
assert.Equal(t, "C:\\Windows\\Microsoft.NET\\Framework\\v3.5\\", pkg.Files[0].Path)
421+
assert.Equal(t, PkgFilesIncluded, pkg.FilesAvailable)
422+
423+
// Verify CPE generation
424+
require.Len(t, pkg.CPEs, 2, "expected 2 CPE entries")
425+
expectedCPEs := []string{
426+
"cpe:2.3:a:microsoft:microsoft_.net_framework:3.5.30729.4926:*:*:*:*:*:*:*",
427+
"cpe:2.3:a:microsoft:microsoft_.net_framework:3.5.30729:*:*:*:*:*:*:*",
428+
}
429+
assert.ElementsMatch(t, expectedCPEs, pkg.CPEs)
430+
})
431+
432+
t.Run("with empty items", func(t *testing.T) {
433+
// Create empty registry items
434+
items := []registry.RegistryKeyItem{}
435+
436+
pkg := getDotNetFrameworkPackageFromRegistryKeyItems(items, platform)
437+
assert.Nil(t, pkg, "expected no package when no items are provided")
438+
})
439+
}

0 commit comments

Comments
 (0)