NET-and-Visual-C

Identify .NET and Visual C++ Dependencies Using PowerShell

Have you ever found yourself patching software vulnerabilities and come across a vulnerability for older .NET or Visual C++ packages but didn’t know if removing the packages would cause issues with random software? If so, you came to the right place. Here I will go over how to Identify .NET and Visual C++ Dependencies Using PowerShell

I was recently patching some clients’ machines and kept seeing Visual C++ from 2008, 2010, and 2011, which of course I wanted to promptly remove. However, after some further research, I discovered that these old Visual C++ versions were actually dependencies for some proprietary software my client was using. Of course, this isn’t the place to discuss why my clients are still using almost decade-old software — Trust me, it irritates me too.

While working on this, I needed a way to tell which software relies on which versions of .NET and Visual C++, so I developed my own PowerShell script to accomplish just that. Of course, I could achieve the same results in almost any other language, but since my clients use Windows and our RMM agent utilizes PowerShell, it seemed appropriate.

I designed the script to scan the entire system and output a CSV file with the relevant data, making it easier to identify which versions of .NET and Visual C++ are necessary for which applications. This method ensures that we don’t accidentally remove a critical dependency and cause unforeseen issues.

The PowerShell Script

Here’s the PowerShell script I created to scan the computer for installed .NET (host and runtime), Visual C++ Redistributable, and ASP.NET versions, and determine which software requires which versions:

#
# Program Name: .NET and Visual C++ Determinier
#
# Description: This will look through all installed applications and tell you which app requires which version of .NET and Visual C++.
#              Output will be displayed through the terminal and written to a CSV file ("C:\ProgramDependencies.csv")
#
# Author: Quynn Bell
#
# Date Modified: 23rd of May 2024
#

# Function to get installed programs
function Get-InstalledPrograms {
    $programs = Get-ItemProperty "HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall\*" -ErrorAction SilentlyContinue |
                Select-Object DisplayName, DisplayVersion, InstallDate, Publisher, InstallLocation

    $programs += Get-ItemProperty "HKLM:\Software\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*" -ErrorAction SilentlyContinue |
                 Select-Object DisplayName, DisplayVersion, InstallDate, Publisher, InstallLocation

    $programs | Where-Object { $_.DisplayName } | Sort-Object DisplayName
}

# Function to search for dependencies in program files
function Get-ProgramDependencies {
    param(
        [string]$installLocation,
        [string[]]$patterns
    )
    $dependencies = @()
    foreach ($pattern in $patterns) {
        $dependencies += Get-ChildItem -Path $installLocation -Recurse -Filter $pattern -ErrorAction SilentlyContinue
    }
    $dependencies
}

# Function to get installed .NET versions
function Get-DotNetVersion {
    param(
        [string]$filePath
    )
    try {
        $versionInfo = [System.Diagnostics.FileVersionInfo]::GetVersionInfo($filePath)
        return $versionInfo.ProductVersion
    } catch {
        return $null
    }
}

# Get installed programs
$installedPrograms = Get-InstalledPrograms

# Define patterns for common .NET and Visual C++ dependencies
$dotNetPatterns = @("System.*.dll", "mscorlib.dll")
$vcppPatterns = @("msvcp*.dll", "msvcr*.dll", "vccorlib*.dll", "vcomp*.dll", "vcruntime*.dll")

# Collect dependency information
$dependencyInfo = @()

foreach ($program in $installedPrograms) {
    if ($program.InstallLocation) {
        # Check for .NET dependencies
        $dotNetDependencies = Get-ProgramDependencies -installLocation $program.InstallLocation -patterns $dotNetPatterns
        foreach ($dependency in $dotNetDependencies) {
            $dotNetVersion = Get-DotNetVersion -filePath $dependency.FullName
            if ($dotNetVersion) {
                $dependencyInfo += [PSCustomObject]@{
                    Program       = $program.DisplayName
                    Version       = $program.DisplayVersion
                    Dependency    = $dependency.Name
                    DependencyType = "DotNet"
                    DependencyVersion = $dotNetVersion
                }
            }
        }
        
        # Check for Visual C++ dependencies
        $vcppDependencies = Get-ProgramDependencies -installLocation $program.InstallLocation -patterns $vcppPatterns
        foreach ($dependency in $vcppDependencies) {
            $vcppVersion = Get-DotNetVersion -filePath $dependency.FullName  # Using same function to get version
            if ($vcppVersion) {
                $dependencyInfo += [PSCustomObject]@{
                    Program       = $program.DisplayName
                    Version       = $program.DisplayVersion
                    Dependency    = $dependency.Name
                    DependencyType = "VisualCpp"
                    DependencyVersion = $vcppVersion
                }
            }
        }
    }
}

# Output the dependency information
$dependencyInfo | Format-Table -AutoSize

# Save to a CSV file for easier visualization
$outputPath = "C:\ProgramDependencies.csv"
$dependencyInfo | Export-Csv -Path $outputPath -NoTypeInformation

Write-Output "Dependency information saved to $outputPath"

How the Script Works

  1. Get-InstalledPrograms: This function retrieves all installed programs from the registry.
  2. Get-ProgramDependencies: This function searches the installation directories of programs for specific patterns indicating .NET or Visual C++ dependencies.
  3. Get-DotNetVersion: This function retrieves the version information of a given file, used to identify the .NET version.
  4. Search Patterns: The script checks for .NET and Visual C++ dependencies by looking for common DLL names and retrieves their version information.
  5. Output: The script outputs the dependency information in a table format and saves it to a CSV file on the root of the C: drive for easy access.

Conclusion

This script provides a practical solution for IT professionals who need to manage software dependencies on Windows machines. By identifying which versions of .NET and Visual C++ are required by installed applications, you can make informed decisions about patching or removing old versions without causing disruptions. This approach has saved me countless hours and headaches, and I hope it does the same for you.

If you have any questions or need further assistance, feel free to leave a comment below or reach out directly. Happy patching!

Leave a Reply