# How to Check the .NET Versions Installed on Windows with Custom Scripts?

Executive Summary
-----------------

Auditing the .NET versions installed on Windows prevents launch failures, verifies software compatibility, and detects end-of-life (EOL) runtimes before rolling out new enterprise applications. However, devices frequently run multiple .NET versions side by side. Because each version family stores installation metadata in different locations, such as standalone registry keys and local filesystem paths, manually tracking them across an enterprise fleet is impractical.

Deployed through Hexnode UEM’s [Execute Custom Script](https://www.hexnode.com/mobile-device-management/help/executing-custom-scripts-for-windows/) feature, the PowerShell and Batch scripts in this guide automate audit runtimes under the local *NT AUTHORITY\\SYSTEM* context. They inspect native 64-bit registry keys, detect installed *dotnet.exe* runtimes, and returns a unified report straight back to the Hexnode UEM console without requiring external tools or user prompts.

Use Cases and Implementation Scenarios
--------------------------------------

- **Validating Environmental Dependencies for Applications:** Verify that target endpoints have the required .NET runtime versions installed before pushing enterprise applications that rely on them.
- **Security and Compliance Auditing:** Identify devices running older, unsupported .NET versions that require security patching or upgrades to comply with organizational security baselines.
- **OS Upgrade Readiness Assessments:** Audit endpoints prior to major Windows OS migrations to record installed .NET versions, making it easy to identify and restore runtime dependencies required by critical applications post-upgrade.
- **Helpdesk Troubleshooting:** Remotely inspect runtime versions from the Hexnode UEM console when troubleshooting third-party software installation failures or runtime crashes reported by end users.

Prerequisites for Executing the Scripts
---------------------------------------

To ensure the successful deployment of these .NET audit scripts, the target Windows endpoints must meet the following requirements.

- **Supported OS:** Windows 10/11 (Home, Pro, Enterprise, Education) and Windows Server (2016, 2019, 2022, and 2025).
- **Architecture Support:** Compatible with x64, x86, and ARM64 system architectures.
- **Management:** Hexnode UEM agent must be installed and initialized.

PowerShell Script to Check Installed .NET Versions
--------------------------------------------------

This PowerShell script inspects Windows registry keys to map the active .NET Framework 4.x Release key to its corresponding release version, audits legacy .NET 3.5 version, and executes *dotnet.exe* to retrieve all modern .NET Core / 5+ runtimes.

PowerShell script to check installed .NET versions

<# .SYNOPSIS Audits installed .NET Framework, legacy .NET, and modern .NET runtimes on Windows endpoints. #> try { # Header Write-Host ("{0,-38} {1}" -f "TYPE", "VERSION") Write-Host ("{0,-38} {1}" -f "--------------------------------------", "----------------") # 1. Classic .NET Framework 4.x (Native 64-bit Registry) $regKey = \[Microsoft.Win32.RegistryKey\]::OpenBaseKey( \[Microsoft.Win32.RegistryHive\]::LocalMachine, \[Microsoft.Win32.RegistryView\]::Registry64 ).OpenSubKey("SOFTWARE\\Microsoft\\NET Framework Setup\\NDP\\v4\\Full") if ($null -ne $regKey) { $val = \[int\]$regKey.GetValue("Release", 0) $regKey.Close() if ($val -gt 0) { $ver = "4.x ($val)" if ($val -ge 533320) { $ver = "4.8.1" } elseif ($val -ge 528040) { $ver = "4.8" } elseif ($val -ge 461808) { $ver = "4.7.2" } elseif ($val -ge 461308) { $ver = "4.7.1" } elseif ($val -ge 460798) { $ver = "4.7" } elseif ($val -ge 394802) { $ver = "4.6.2" } elseif ($val -ge 394254) { $ver = "4.6.1" } elseif ($val -ge 393295) { $ver = "4.6" } elseif ($val -ge 379893) { $ver = "4.5.2" } Write-Host ("{0,-38} {1}" -f ".NET Framework", $ver) } } # 2. Legacy .NET Framework (3.5) $legKey = \[Microsoft.Win32.RegistryKey\]::OpenBaseKey( \[Microsoft.Win32.RegistryHive\]::LocalMachine, \[Microsoft.Win32.RegistryView\]::Registry64 ).OpenSubKey("SOFTWARE\\Microsoft\\NET Framework Setup\\NDP\\v3.5") if ($null -ne $legKey) { $legVal = \[string\]$legKey.GetValue("Version", "") $legKey.Close() if ($legVal) { Write-Host ("{0,-38} {1}" -f ".NET Framework (Legacy)", $legVal) } } # 3. Modern .NET Core / 5+ Runtimes (Dynamic Multi-Path) $candidatePaths = @( "$env:ProgramFiles\\dotnet\\dotnet.exe", "$env:ProgramW6432\\dotnet\\dotnet.exe", "${env:ProgramFiles(x86)}\\dotnet\\dotnet.exe" ) $resolvedDotnet = $null foreach ($p in $candidatePaths) { if (Test-Path $p) { $resolvedDotnet = $p break } } if (-not $resolvedDotnet) { $cmdCheck = Get-Command dotnet.exe -ErrorAction SilentlyContinue if ($cmdCheck) { $resolvedDotnet = $cmdCheck.Source } } if ($resolvedDotnet) { $runtimes = & $resolvedDotnet --list-runtimes 2>$null if ($runtimes) { foreach ($r in $runtimes) { $parts = -split $r if ($parts.Count -ge 2) { Write-Host ("{0,-38} {1}" -f $parts\[0\], $parts\[1\]) } } } else { Write-Host ("{0,-38} {1}" -f "Modern .NET (dotnet.exe)", "No Runtimes") } } else { Write-Host ("{0,-38} {1}" -f "Modern .NET (dotnet.exe)", "Not Detected") } exit 0 } catch { Write-Error "Script execution failed: $($\_.Exception.Message)" exit 1 }

   1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

  <\#

.SYNOPSIS

 Audits installed .NET Framework, legacy .NET, and modern .NET runtimes on Windows endpoints.

\#>

try {

 \# Header

 Write-Host ("{0,-38} {1}" -f "TYPE", "VERSION")

 Write-Host ("{0,-38} {1}" -f "--------------------------------------", "----------------")

 \# 1. Classic .NET Framework 4.x (Native 64-bit Registry)

 $regKey = \[Microsoft.Win32.RegistryKey\]::OpenBaseKey(

 \[Microsoft.Win32.RegistryHive\]::LocalMachine,

 \[Microsoft.Win32.RegistryView\]::Registry64

 ).OpenSubKey("SOFTWARE\\Microsoft\\NET Framework Setup\\NDP\\v4\\Full")

 if ($null -ne $regKey) {

 $val = \[int\]$regKey.GetValue("Release", 0)

 $regKey.Close()

 if ($val -gt 0) {

 $ver = "4.x ($val)"

 if ($val -ge 533320) { $ver = "4.8.1" }

 elseif ($val -ge 528040) { $ver = "4.8" }

 elseif ($val -ge 461808) { $ver = "4.7.2" }

 elseif ($val -ge 461308) { $ver = "4.7.1" }

 elseif ($val -ge 460798) { $ver = "4.7" }

 elseif ($val -ge 394802) { $ver = "4.6.2" }

 elseif ($val -ge 394254) { $ver = "4.6.1" }

 elseif ($val -ge 393295) { $ver = "4.6" }

 elseif ($val -ge 379893) { $ver = "4.5.2" }

 Write-Host ("{0,-38} {1}" -f ".NET Framework", $ver)

 }

 }

 \# 2. Legacy .NET Framework (3.5)

 $legKey = \[Microsoft.Win32.RegistryKey\]::OpenBaseKey(

 \[Microsoft.Win32.RegistryHive\]::LocalMachine,

 \[Microsoft.Win32.RegistryView\]::Registry64

 ).OpenSubKey("SOFTWARE\\Microsoft\\NET Framework Setup\\NDP\\v3.5")

 if ($null -ne $legKey) {

 $legVal = \[string\]$legKey.GetValue("Version", "")

 $legKey.Close()

 if ($legVal) {

 Write-Host ("{0,-38} {1}" -f ".NET Framework (Legacy)", $legVal)

 }

 }

 \# 3. Modern .NET Core / 5+ Runtimes (Dynamic Multi-Path)

 $candidatePaths = @(

 "$env:ProgramFiles\\dotnet\\dotnet.exe",

 "$env:ProgramW6432\\dotnet\\dotnet.exe",

 "${env:ProgramFiles(x86)}\\dotnet\\dotnet.exe"

 )

 $resolvedDotnet = $null

 foreach ($p in $candidatePaths) {

 if (Test-Path $p) {

 $resolvedDotnet = $p

 break

 }

 }

 if (-not $resolvedDotnet) {

 $cmdCheck = Get-Command dotnet.exe -ErrorAction SilentlyContinue

 if ($cmdCheck) { $resolvedDotnet = $cmdCheck.Source }

 }

 if ($resolvedDotnet) {

 $runtimes = & $resolvedDotnet --list-runtimes 2>$null

 if ($runtimes) {

 foreach ($r in $runtimes) {

 $parts = -split $r

 if ($parts.Count -ge 2) {

 Write-Host ("{0,-38} {1}" -f $parts\[0\], $parts\[1\])

 }

 }

 } else {

 Write-Host ("{0,-38} {1}" -f "Modern .NET (dotnet.exe)", "No Runtimes")

 }

 } else {

 Write-Host ("{0,-38} {1}" -f "Modern .NET (dotnet.exe)", "Not Detected")

 }

 exit 0

}

catch {

 Write-Error "Script execution failed: $($\_.Exception.Message)"

 exit 1

}

   

 

  

![Screenshot of Action History tab on the device details page of Hexnode UEM displaying .NET versions installed on the Windows endpoint using PowerShell script.](https://cdn.hexnode.com/mobile-device-management/help/wp-content/uploads/2026/11/Checking-the-.NET-versions-installed-on-Windows-via-PowerShell-script-300x140.png)

Batch Script to Check Installed .NET Versions
---------------------------------------------

This Batch script uses native *reg query* commands to extract the active .NET Framework 4.x Release DWORD, converts hexadecimal value to decimal to map the release version, checks for legacy .NET 3.5 version, and executes *dotnet.exe* to list modern runtimes.

Batch script to check installed .NET versions

@echo off setlocal enabledelayedexpansion :: ============================================================================== :: Batch: .NET Version Audit :: ============================================================================== echo TYPE VERSION echo -------------------------------------- ---------------- set "AUDIT\_FOUND=0" :: 1. Active .NET Framework 4.x (Native 64-bit Registry) for /f "tokens=3" %%a in ('reg query "HKLM\\SOFTWARE\\Microsoft\\NET Framework Setup\\NDP\\v4\\Full" /v Release /reg:64 2^>nul ^| findstr /i "Release"') do ( set /a relDec=%%a set "fwVer=4.x (!relDec!)" if !relDec! geq 533320 set "fwVer=4.8.1" if !relDec! geq 528040 if !relDec! lss 533320 set "fwVer=4.8" if !relDec! geq 461808 if !relDec! lss 528040 set "fwVer=4.7.2" if !relDec! geq 461308 if !relDec! lss 461808 set "fwVer=4.7.1" if !relDec! geq 460798 if !relDec! lss 461308 set "fwVer=4.7" if !relDec! geq 394802 if !relDec! lss 460798 set "fwVer=4.6.2" if !relDec! geq 394254 if !relDec! lss 394802 set "fwVer=4.6.1" if !relDec! geq 393295 if !relDec! lss 394254 set "fwVer=4.6" if !relDec! geq 379893 if !relDec! lss 393295 set "fwVer=4.5.2" set "pad=.NET Framework " echo !pad:~0,38! !fwVer! set "AUDIT\_FOUND=1" ) :: 2. Legacy .NET Framework (3.5) for /f "tokens=3" %%a in ('reg query "HKLM\\SOFTWARE\\Microsoft\\NET Framework Setup\\NDP\\v3.5" /v Version /reg:64 2^>nul ^| findstr /i "Version"') do ( set "padLeg=.NET Framework (Legacy) " echo !padLeg:~0,38! %%a set "AUDIT\_FOUND=1" ) :: 3. Modern .NET Core / 5+ Runtimes (Multi-Path Detection) set "DOTNET\_BIN=" if exist "%ProgramFiles%\\dotnet\\dotnet.exe" set "DOTNET\_BIN=%ProgramFiles%\\dotnet\\dotnet.exe" if not defined DOTNET\_BIN if exist "%ProgramW6432%\\dotnet\\dotnet.exe" set "DOTNET\_BIN=%ProgramW6432%\\dotnet\\dotnet.exe" if not defined DOTNET\_BIN if exist "%ProgramFiles(x86)%\\dotnet\\dotnet.exe" set "DOTNET\_BIN=%ProgramFiles(x86)%\\dotnet\\dotnet.exe" if defined DOTNET\_BIN ( set "HAS\_RUNTIMES=0" for /f "tokens=1,2" %%a in ('"%DOTNET\_BIN%" --list-runtimes 2^>nul') do ( set "HAS\_RUNTIMES=1" set "padMod=%%a " echo !padMod:~0,38! %%b ) if !HAS\_RUNTIMES! equ 0 ( set "padNone=Modern .NET (dotnet.exe) " echo !padNone:~0,38! No Runtimes ) set "AUDIT\_FOUND=1" ) else ( set "padMiss=Modern .NET (dotnet.exe) " echo !padMiss:~0,38! Not Detected ) :: Exit with failure only if no audit checks could be read at all if !AUDIT\_FOUND! equ 0 ( echo Error: Failed to query registry or detect runtime environments. >&2 exit /b 1 ) exit /b 0

   1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

  @echo off

setlocal enabledelayedexpansion

:: ==============================================================================

:: Batch: .NET Version Audit

:: ==============================================================================

echo TYPE VERSION

echo -------------------------------------- ----------------

set "AUDIT\_FOUND=0"

:: 1. Active .NET Framework 4.x (Native 64-bit Registry)

for /f "tokens=3" %%a in ('reg query "HKLM\\SOFTWARE\\Microsoft\\NET Framework Setup\\NDP\\v4\\Full" /v Release /reg:64 2^>nul ^| findstr /i "Release"') do (

 set /a relDec=%%a

 set "fwVer=4.x (!relDec!)"

 if !relDec! geq 533320 set "fwVer=4.8.1"

 if !relDec! geq 528040 if !relDec! lss 533320 set "fwVer=4.8"

 if !relDec! geq 461808 if !relDec! lss 528040 set "fwVer=4.7.2"

 if !relDec! geq 461308 if !relDec! lss 461808 set "fwVer=4.7.1"

 if !relDec! geq 460798 if !relDec! lss 461308 set "fwVer=4.7"

 if !relDec! geq 394802 if !relDec! lss 460798 set "fwVer=4.6.2"

 if !relDec! geq 394254 if !relDec! lss 394802 set "fwVer=4.6.1"

 if !relDec! geq 393295 if !relDec! lss 394254 set "fwVer=4.6"

 if !relDec! geq 379893 if !relDec! lss 393295 set "fwVer=4.5.2"

 set "pad=.NET Framework "

 echo !pad:~0,38! !fwVer!

 set "AUDIT\_FOUND=1"

)

:: 2. Legacy .NET Framework (3.5)

for /f "tokens=3" %%a in ('reg query "HKLM\\SOFTWARE\\Microsoft\\NET Framework Setup\\NDP\\v3.5" /v Version /reg:64 2^>nul ^| findstr /i "Version"') do (

 set "padLeg=.NET Framework (Legacy) "

 echo !padLeg:~0,38! %%a

 set "AUDIT\_FOUND=1"

)

:: 3. Modern .NET Core / 5+ Runtimes (Multi-Path Detection)

set "DOTNET\_BIN="

if exist "%ProgramFiles%\\dotnet\\dotnet.exe" set "DOTNET\_BIN=%ProgramFiles%\\dotnet\\dotnet.exe"

if not defined DOTNET\_BIN if exist "%ProgramW6432%\\dotnet\\dotnet.exe" set "DOTNET\_BIN=%ProgramW6432%\\dotnet\\dotnet.exe"

if not defined DOTNET\_BIN if exist "%ProgramFiles(x86)%\\dotnet\\dotnet.exe" set "DOTNET\_BIN=%ProgramFiles(x86)%\\dotnet\\dotnet.exe"

if defined DOTNET\_BIN (

 set "HAS\_RUNTIMES=0"

 for /f "tokens=1,2" %%a in ('"%DOTNET\_BIN%" --list-runtimes 2^>nul') do (

 set "HAS\_RUNTIMES=1"

 set "padMod=%%a "

 echo !padMod:~0,38! %%b

 )

 if !HAS\_RUNTIMES! equ 0 (

 set "padNone=Modern .NET (dotnet.exe) "

 echo !padNone:~0,38! No Runtimes

 )

 set "AUDIT\_FOUND=1"

) else (

 set "padMiss=Modern .NET (dotnet.exe) "

 echo !padMiss:~0,38! Not Detected

)

:: Exit with failure only if no audit checks could be read at all

if !AUDIT\_FOUND! equ 0 (

 echo Error: Failed to query registry or detect runtime environments. >&2

 exit /b 1

)

exit /b 0

   

 

  

![Screenshot of Action History tab on the device details page of Hexnode UEM displaying .NET versions installed on the Windows endpoint using Batch script.](https://cdn.hexnode.com/mobile-device-management/help/wp-content/uploads/2026/11/Checking-the-.NET-versions-installed-on-Windows-via-Batch-script-300x140.png)

How to Deploy the Script via Hexnode UEM?
-----------------------------------------

1. Save either the PowerShell script (as a **.ps1** file) or the Batch script (as a **.bat** file) on your computer, depending on which script you intend to deploy.
2. Log in to your Hexnode UEM portal.
3. Navigate to **Manage > Endpoints** and select the target Windows device(s).
4. Click **Actions > Deployments > Execute Custom Script**.
5. Upload the saved script file and click **Execute**.

How to Verify Script Output in Hexnode UEM?
-------------------------------------------

To check the execution status and view the script output:

1. Navigate to **Manage > Endpoints** and select the target endpoint in the Hexnode UEM portal.
2. Click the **Action History** tab to view the log of actions dispatched to the endpoint.
3. Locate the **Execute Custom Script** entry in the action list. 
    - If Status is **“Success”**: Click the **Show Output** button to inspect the formatted table displaying active .NET Framework releases, legacy .NET 3.5, and modern .NET runtimes.
    - If Status is **“Failed”**: Hover over the information icon (i) next to the status badge to review the execution error message and script error output.
    - If Status is **“Pending/In progress”**: The target endpoint is powered off, disconnected from the network, or sleeping. The script action remains queued in Hexnode UEM and will execute automatically when the device next checks in.

Key Technical Insights and Architecture Notes
---------------------------------------------

- **.NET Framework 4.x Latest Version Reporting:** .NET Framework 4.x updates install as in-place replacements. Because Windows stores .NET 4.x metadata in a single registry value that gets overwritten with every update, older 4.x entries no longer exist on the system. The script therefore reads and reports only the single active, highest version installed (such as 4.8.1).
- **Side-by-Side Modern Runtimes:** Unlike .NET Framework, modern .NET (.NET Core and .NET 5+) versions run side-by-side in distinct installation folders. The script lists every installed major, minor, and patch release, as well as distinct workload packages (*Microsoft.NETCore.App*, *Microsoft.WindowsDesktop.App*, and *Microsoft.AspNetCore.App*) individually. Note that these scripts report globally installed shared runtimes and excludes private, self-contained application bundles.
- **Explicit Verification for Missing Runtimes:** If modern .NET is not deployed on an endpoint, the script does not skip the validation or fail silently. It explicitly reports *Modern .NET (dotnet.exe) Not Detected* (or *No Runtimes*), providing administrators with affirmative confirmation that the check was performed.
- **Modern .NET Detection Across ARM64 Endpoints:** On ARM64 devices (such as Microsoft Surface Pro X or Snapdragon X Elite endpoints), modern .NET can exist as a native ARM64 runtime or under an x64/x86 emulation directory. The scripts dynamically prioritize native 64-bit paths before checking architectural fallback locations, ensuring that installed runtimes are accurately reported without being misidentified by emulation layers.

Troubleshooting Common Issues
-----------------------------

IssueProbable CauseSolution**Action History status shows “Pending” indefinitely.**The target device is offline, powered off, or unable to reach the Hexnode UEM server.Ensure that the endpoint is connected to an active network. The action will be executed automatically once the device checks back in with Hexnode UEM.**Modern .NET packages appear in Installed Apps on the endpoint, but script output shows “Not Detected”.**The installed runtime is inaccessible to the 64-bit *SYSTEM* audit context. This typically occurs under two conditions: - **Architecture mismatch:** A 32-bit (x86) runtime is installed on a 64-bit operating system. The script specifically inspects the native 64-bit directory (`%ProgramFiles%\dotnet`) to ensure 64-bit enterprise application compatibility.
- **Per-user installation:** The runtime was installed within a specific user profile rather than machine-wide, leaving the *dotnet.exe* host unregistered in the system-level *PATH* environment variable.

- **Resolve architecture mismatches:** Deploy the official 64-bit (x64) runtime package (e.g., `dotnet-runtime-<version>-win-x64.exe` or the x64 Windows Desktop Runtime) to the target endpoint.
- **Resolve per-user installations:** Reinstall the modern .NET runtime system-wide or manually append the runtime directory to the system-level *PATH* variable. Once applied, re-execute the audit script to verify the detection.

**Script output displays only legacy .NET (missing .NET 4.x).**The registry key *HKLM\\SOFTWARE\\Microsoft\\NET Framework Setup\\NDP\\v4\\Full* is missing or corrupted on the target endpoint. The script runs successfully but cleanly skips .NET 4.x detection because the key cannot be read.On modern Windows operating systems, .NET Framework 4.x is an integrated OS component maintained by the Windows Component-Based Servicing (CBS) engine. Resolve the corruption by performing the following remediation workflow: - **Repair the component store:** Execute the following native commands on the affected endpoint to repair the underlying OS component store and restore the missing registry definitions: `DISM.exe /Online /Cleanup-Image /RestoreHealth`
    
    `sfc /scannow`
- **Restart the endpoint:** Initiate a device reboot to ensure the repaired registry configurations and system files are fully applied.
- **Re-execute the script:** Run the script again via Hexnode UEM to verify that the .NET Framework 4.x is successfully detected.