Skip to main content

How to Report on Installed Windows Features in Batch Script

Windows Features, like Hyper-V, WSL (Windows Subsystem for Linux), or legacy components like SMB 1.0, define what your computer is capable of doing. For system administrators, ensuring that specific features are either enabled (for developers) or disabled (for security) is a constant task. A Batch script can query the system to generate a complete report of which features are active, disabled, or have their installation payloads removed.

This guide will explain how to audit and report on Windows optional features using DISM and PowerShell.

Method 1: Full Feature Report Using DISM

DISM (Deployment Image Servicing and Management) is the standard command-line tool for querying Windows features. It provides a comprehensive list of every optional feature and its current state.

Implementation

@echo off
setlocal

set "ReportFile=%~dp0windows_features_%COMPUTERNAME%.txt"

:: Verify admin privileges (required for DISM)
net session >nul 2>&1
if errorlevel 1 (
echo [ERROR] DISM requires administrator privileges. >&2
echo Right-click and select "Run as administrator." >&2
endlocal
exit /b 1
)

echo [INFO] Querying Windows Features...

:: Generate a timestamped report header
(
echo ==================================================
echo Windows Features Report
echo Computer: %COMPUTERNAME%
echo Date: %date% %time%
echo ==================================================
echo.
) > "%ReportFile%"

:: Append the feature table
dism /online /get-features /format:table >> "%ReportFile%" 2>&1

if errorlevel 1 (
echo [ERROR] DISM query failed. >&2
endlocal
exit /b 1
)

echo [OK] Feature report saved to: %ReportFile%

endlocal
exit /b 0

DISM feature states:

StateMeaning
EnabledFeature is installed and active
DisabledFeature is not active but installation files are available
Disabled with Payload RemovedFeature is disabled AND its installation files have been removed to save disk space. Requires internet or installation media to re-enable.
Enable PendingFeature is being enabled and requires a reboot

When to use DISM vs. PowerShell:

DISM works on all Windows editions including Server Core, Windows PE, and offline images. PowerShell's Get-WindowsOptionalFeature (Method 3) is more flexible for scripting but may not be available in minimal environments.

Method 2: Checking Specific Features (Security and Compliance)

Rather than reviewing a full feature list, you typically need to verify that specific features are in a required state, enabled for functionality or disabled for security.

@echo off
setlocal EnableDelayedExpansion

:: Verify admin privileges
net session >nul 2>&1
if errorlevel 1 (
echo [ERROR] Administrator privileges required. >&2
endlocal
exit /b 1
)

echo [INFO] Security feature compliance check:
echo --------------------------------------------------

:: Define features and their expected states
:: Format: FeatureName|ExpectedState|Reason
set "Check[0]=SMB1Protocol|Disabled|Legacy protocol vulnerable to WannaCry/EternalBlue"
set "Check[1]=TelnetClient|Disabled|Transmits credentials in plaintext"
set "Check[2]=Microsoft-Hyper-V-All|Enabled|Required for development VMs"
set "Check[3]=Microsoft-Windows-Subsystem-Linux|Enabled|Required for WSL development"
set "CheckCount=4"

set "Failures=0"

for /L %%i in (0, 1, 3) do (
for /f "tokens=1-3 delims=|" %%a in ("!Check[%%i]!") do (
set "Feature=%%a"
set "Expected=%%b"
set "Reason=%%c"

:: Query the feature state via PowerShell (handles localization)
for /f "delims=" %%s in (
'powershell -NoProfile -Command ^
"$f = Get-WindowsOptionalFeature -Online -FeatureName ''!Feature!'' -ErrorAction SilentlyContinue;" ^
"if ($f) { $f.State } else { ''NotFound'' }"'
) do set "State=%%s"

:: Compare actual state against expected
if /i "!State!"=="!Expected!" (
echo [PASS] !Feature!: !State!
) else if "!State!"=="NotFound" (
echo [SKIP] !Feature!: Not available on this edition
) else (
echo [FAIL] !Feature!: !State! (expected: !Expected!^)
echo Reason: !Reason!
set /a "Failures+=1"
)
)
)

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

if !Failures! gtr 0 (
echo [WARNING] !Failures! compliance failure(s^) detected. >&2
endlocal
exit /b 1
) else (
echo [OK] All checked features are in their expected states.
)

endlocal
exit /b 0

Example of Possible output:

[INFO] Security feature compliance check:
--------------------------------------------------
[PASS] SMB1Protocol: Disabled
[FAIL] TelnetClient: Enabled (expected: Disabled)
Reason: Transmits credentials in plaintext
[PASS] Microsoft-Hyper-V-All: Enabled
[SKIP] Microsoft-Windows-Subsystem-Linux: Not available on this edition
--------------------------------------------------
[WARNING] 1 compliance failure(s) detected.

High-risk features to check in security audits:

FeatureRiskRecommendation
SMB1ProtocolVulnerable to WannaCry, EternalBlue, and other exploitsDisable unless legacy devices require it
TelnetClientTransmits credentials and data in plaintextDisable; use SSH instead
TelnetServerSame risks as client, plus exposes a listening serviceDisable
TFTPNo authentication, no encryptionDisable
MicrosoftWindowsPowerShellV2RootPowerShell v2 bypasses modern security controls (AMSI, logging)Disable on secure systems

Method 3: CSV Export for Fleet Auditing

When auditing features across many machines, export structured data to CSV for analysis in Excel, Power BI, or a configuration management database.

@echo off
setlocal

set "OutFile=%~dp0features_%COMPUTERNAME%.csv"

net session >nul 2>&1
if errorlevel 1 (
echo [ERROR] Administrator privileges required. >&2
endlocal
exit /b 1
)

echo [INFO] Exporting Windows Features to CSV...

powershell -NoProfile -Command ^
"$features = Get-WindowsOptionalFeature -Online -ErrorAction Stop |" ^
" Select-Object FeatureName, State;" ^
"$features | Export-Csv -Path '%OutFile%' -NoTypeInformation;" ^
"Write-Host \"Exported $($features.Count) features to: %OutFile%\";" ^
"$enabled = ($features | Where-Object State -eq 'Enabled').Count;" ^
"$disabled = ($features | Where-Object State -eq 'Disabled').Count;" ^
"$removed = ($features | Where-Object State -eq 'DisabledWithPayloadRemoved').Count;" ^
"Write-Host \" Enabled: $enabled Disabled: $disabled Payload Removed: $removed\""

if errorlevel 1 (
echo [ERROR] Feature export failed. >&2
endlocal
exit /b 1
)

endlocal
exit /b 0

Fleet-wide single-line summary:

For collecting one summary line per machine into a shared CSV:

@echo off
setlocal EnableExtensions

set "CSVFile=\\Server\Audit\feature_summary.csv"
set "UNCBase=\\Server\Audit"

echo [INFO] Starting audit...

:: --------------------------------------------------
:: 1. Check network path exists and is reachable
:: --------------------------------------------------
if not exist "%UNCBase%\" (
echo [ERROR] Network share not reachable: %UNCBase%
exit /b 1
)

:: --------------------------------------------------
:: 2. Check PowerShell availability
:: --------------------------------------------------
where powershell >nul 2>nul
if errorlevel 1 (
echo [ERROR] PowerShell not found on this system
exit /b 1
)

:: --------------------------------------------------
:: 3. Check write permission (test file)
:: --------------------------------------------------
set "TESTFILE=%UNCBase%\._write_test_%RANDOM%.tmp"

echo test>"%TESTFILE%" 2>nul
if not exist "%TESTFILE%" (
echo [ERROR] No write permission on %UNCBase%
exit /b 1
)
del "%TESTFILE%" >nul 2>nul

:: --------------------------------------------------
:: 4. Create CSV header if missing
:: --------------------------------------------------
if not exist "%CSVFile%" (
echo "Timestamp","Computer","TotalFeatures","Enabled","Disabled","SMB1","TelnetClient" > "%CSVFile%"
if errorlevel 1 (
echo [ERROR] Failed to create CSV file
exit /b 1
)
)

:: --------------------------------------------------
:: 5. Run PowerShell safely and validate output
:: --------------------------------------------------
powershell -NoProfile -ExecutionPolicy Bypass -Command ^
"try {" ^
" $features = Get-WindowsOptionalFeature -Online -ErrorAction Stop;" ^
" $ts = Get-Date -Format 'yyyy-MM-dd HH:mm:ss';" ^
" $computer = $env:COMPUTERNAME;" ^
" $total = $features.Count;" ^
" if (-not $total) { throw 'No feature data returned' }" ^
" $enabled = ($features | Where-Object State -eq 'Enabled').Count;" ^
" $disabled = $total - $enabled;" ^
" $smb1 = ($features | Where-Object FeatureName -eq 'SMB1Protocol' | Select-Object -First 1).State;" ^
" $telnet = ($features | Where-Object FeatureName -eq 'TelnetClient' | Select-Object -First 1).State;" ^
" if (-not $smb1) { $smb1 = 'N/A' }" ^
" if (-not $telnet) { $telnet = 'N/A' }" ^
" $obj = [pscustomobject]@{" ^
" Timestamp=$ts;Computer=$computer;TotalFeatures=$total;" ^
" Enabled=$enabled;Disabled=$disabled;SMB1=$smb1;TelnetClient=$telnet" ^
" }" ^
" $obj | Export-Csv -Path '%CSVFile%' -Append -NoTypeInformation -Encoding UTF8;" ^
"} catch {" ^
" Write-Host '[ERROR] PowerShell execution failed:' $_.Exception.Message;" ^
" exit 1;" ^
"}"

if errorlevel 1 (
echo [ERROR] Feature collection failed
exit /b 1
)

echo [SUCCESS] Audit completed successfully
endlocal
exit /b 0

How to Avoid Common Errors

Wrong Way: Using wmic product for Features

wmic product queries installed MSI software packages (Office, Chrome, etc.). It does not see Windows Optional Features like IIS, Hyper-V, or .NET Framework components. These are completely separate systems.

Correct Way: Use dism /online /get-features or Get-WindowsOptionalFeature -Online.

Wrong Way: Parsing DISM Text Output with findstr for State Checking

:: UNRELIABLE: localized output breaks this on non-English systems
dism /online /get-features | findstr /i "TelnetClient" | findstr /i "Enabled"

DISM text output is localized. "Enabled" appears as "Aktiviert" (German), "Attivato" (Italian), etc. Additionally, findstr on separate lines doesn't confirm the feature name and state belong to the same entry.

Correct Way: Use Get-WindowsOptionalFeature -Online -FeatureName 'TelnetClient' which returns a typed State property that is language-independent.

Problem: Feature Names Are Case-Sensitive in Some Contexts

While dism is case-insensitive, Get-WindowsOptionalFeature -FeatureName can be case-sensitive depending on the PowerShell version and module implementation.

Solution: Use the exact feature name as listed by dism /online /get-features or Get-WindowsOptionalFeature -Online. When in doubt, query the full list and filter with -like '*keyword*'.

Problem: Features Not Available on All Editions

Windows Home, Pro, Enterprise, and Server editions have different available features. Hyper-V is not available on Home edition. Server-specific roles (like DHCP Server) don't exist on client editions. Your script should handle "feature not found" gracefully rather than reporting it as a compliance failure.

Solution: Method 2 handles this with a "NotFound" state that produces a [SKIP] result instead of a [FAIL].

Best Practices and Rules

1. Use PowerShell for Programmatic Checks

DISM is excellent for generating human-readable reports. For scripted compliance checks (pass/fail decisions, CSV export, conditional logic), use Get-WindowsOptionalFeature, it returns typed objects with consistent property values regardless of OS language.

2. Define a Baseline Feature Set

Document which features should be enabled and which should be disabled for each machine role (developer workstation, production server, domain controller). Use Method 2's compliance check pattern to verify each machine against its role's baseline.

3. Check for "Payload Removed" State

When a feature shows DisabledWithPayloadRemoved, its installation files have been purged from the local component store. Re-enabling it requires internet access or installation media (dism /online /enable-feature /featurename:X /source:D:\sources\sxs). This is important to know before deploying to air-gapped environments.

4. Monitor Security-Sensitive Features

Run Method 2's compliance check as a scheduled task. Legacy features like SMB1 and Telnet can be silently re-enabled by software installers or Group Policy changes. Regular auditing ensures they stay disabled.

5. Collect Fleet-Wide Data

For organizations with many machines, use the fleet summary pattern (Method 3) to collect one line per machine into a shared CSV. Sort by the SMB1 or Telnet columns to quickly identify non-compliant systems.

Conclusions

Reporting on installed Windows Features is a foundational task for maintaining a consistent and secure IT infrastructure. By using DISM for comprehensive reports, PowerShell for language-independent compliance checking, and CSV exports for fleet-wide auditing, you replace manual inspection with reliable, automated verification. This capability ensures that your systems have the components they need to function while keeping unnecessary and insecure legacy features disabled.