How to Enable Optional Windows Features on Workstations Using PowerShell

Managing Windows workstations at scale often requires automation and standardization, particularly in environments where compliance, security, and operational consistency are non-negotiable. One frequently encountered administrative task is enabling optional Windows features—components that are built into the OS but not installed by default. This is where PowerShell, Microsoft’s robust scripting language, becomes indispensable. This blog post explores a powerful PowerShell script designed to enable optional Windows features on workstations efficiently and securely.

Background

Optional Windows features are system-level components such as .NET Framework versions, Windows Identity Foundation, or WCF services. These are often prerequisites for enterprise applications and custom software solutions. However, manually enabling them across multiple endpoints can become tedious and error-prone, especially for Managed Service Providers (MSPs) and internal IT departments.

The script in question was developed with that exact challenge in mind. It offers a secure, repeatable, and automated method to manage Windows feature states while incorporating essential safeguards. By integrating checks for server environments, elevated privileges, and excluding high-risk components, the script is tailored for professional IT use.

The Script:

#Requires -Version 5.1

<#

.SYNOPSIS
    Enable Optional Windows Features on Workstations.

.DESCRIPTION
    Enable Optional Windows Features on Workstations.

By using this script, you indicate your acceptance of the following legal terms as well as our Terms of Use at https://www.ninjaone.com/terms-of-use.
    Ownership Rights: NinjaOne owns and will continue to own all right, title, and interest in and to the script (including the copyright). NinjaOne is giving you a limited license to use the script in accordance with these legal terms. 
    Use Limitation: You may only use the script for your legitimate personal or internal business purposes, and you may not share the script with another party. 
    Republication Prohibition: Under no circumstances are you permitted to re-publish the script in any script library or website belonging to or under the control of any other software provider. 
    Warranty Disclaimer: The script is provided “as is” and “as available”, without warranty of any kind. NinjaOne makes no promise or guarantee that the script will be free from defects or that it will meet your specific needs or expectations. 
    Assumption of Risk: Your use of the script is at your own risk. You acknowledge that there are certain inherent risks in using the script, and you understand and assume each of those risks. 
    Waiver and Release: You will not hold NinjaOne responsible for any adverse or unintended consequences resulting from your use of the script, and you waive any legal or equitable rights or remedies you may have against NinjaOne relating to your use of the script. 
    EULA: If you are a NinjaOne customer, your use of the script is subject to the End User License Agreement applicable to you (EULA).

.PARAMETER GetFeatureNameList
    If selected, the script will list all available features to enable.

.PARAMETER FeatureNameToInstall
    The name of the feature to enable.

.PARAMETER RestartIfNeeded
    If selected, the system will RestartIfNeeded after enabling the feature.

.EXAMPLE
    -FeatureNameToInstall "Windows-Identity-Foundation"
    [Info] Enabling Windows-Identity-Foundation

.EXAMPLE
    -GetFeatureNameList
    [Info] The following features are available to enable:
    SimpleTCP
    Windows-Identity-Foundation
    WCF-HTTP-Activation
    WCF-NonHTTP-Activation
    WCF-HTTP-Activation45
    WCF-TCP-Activation45
    WCF-Pipe-Activation45
    WCF-MSMQ-Activation45
    DataCenterBridging
    Windows-Defender-Default-Definitions


.NOTES
    Minimum OS Architecture Supported: Windows 10
    Release Notes: Initial Release

#>

param (
    [switch]$GetFeatureNameList,
    [string]$FeatureNameToInstall,
    [switch]$RestartIfNeeded,
    [switch]$InstallParentOrDefaultFeatures
)

begin {
    function Test-IsServer {
        # Determine the method to retrieve the operating system information based on PowerShell version
        try {
            $OS = if ($PSVersionTable.PSVersion.Major -lt 3) {
                Get-WmiObject -Class Win32_OperatingSystem -ErrorAction Stop
            }
            else {
                Get-CimInstance -ClassName Win32_OperatingSystem -ErrorAction Stop
            }
        }
        catch {
            Write-Host -Object "[Error] Unable to validate whether or not this device is a server."
            Write-Host -Object "[Error] $($_.Exception.Message)"
            exit 1
        }
    
        # Check if the ProductType is "2", which indicates that the system is a domain controller or is a server
        if ($OS.ProductType -eq "2" -or $OS.ProductType -eq "3") {
            return $true
        }
    }

    function Test-IsElevated {
        # Get the current Windows identity of the user running the script
        $id = [System.Security.Principal.WindowsIdentity]::GetCurrent()
    
        # Create a WindowsPrincipal object based on the current identity
        $p = New-Object System.Security.Principal.WindowsPrincipal($id)
    
        # Check if the current user is in the Administrator role
        # The function returns $True if the user has administrative privileges, $False otherwise
        $p.IsInRole([System.Security.Principal.WindowsBuiltInRole]::Administrator)
    }

    function Set-Feature {
        param (
            [string]$FeatureName
        )
        $script:ShouldRestart = $false

        $Splat = @{
            Online      = $true
            FeatureName = $FeatureName
            All         = $InstallParentOrDefaultFeatures
            NoRestart   = $true
            ErrorAction = 'Stop'
        }

        try {
            # Enable the Windows Optional Feature
            Write-Host -Object "[Info] Enabling $FeatureName"
            if ($RestartIfNeeded) {
                $(Enable-WindowsOptionalFeature @Splat -WarningVariable warn -ErrorVariable err -InformationVariable info -WarningAction SilentlyContinue) | Where-Object {
                    $_.RestartNeeded
                } | ForEach-Object {
                    $script:ShouldRestart = $true
                } | Out-Null
            }
            else {
                $(Enable-WindowsOptionalFeature @Splat -WarningVariable warn -ErrorVariable err -InformationVariable info -WarningAction SilentlyContinue) | Out-Null
            }
            Write-Host -Object "[Info] Enabled $FeatureName"
        }
        catch {
            Write-Host -Object "[Error] Unable to enable $FeatureName"
            if ($err) {
                Write-Host -Object "[Error] $($err.Exception.Message)"
            }
            else {
                Write-Host -Object "[Error] $($_.Exception.Message)"
            }
            exit 1
        }
        return $script:ShouldRestart
    }

    # List of features to exclude for security reasons
    $FeaturesToExclude = @(
        "SMB1Protocol",
        "SMB1Protocol-Client",
        "SMB1Protocol-Server",
        "TelnetClient",
        "TFTP",
        "IIS-FTPServer",
        "IIS-FTPSvc",
        "DirectPlay",
        "MicrosoftWindowsPowerShellV2",
        "MicrosoftWindowsPowerShellV2Root",
        "LegacyComponents",
        "IIS-CGI",
        "IIS-ASP",
        "IIS-ISAPIExtensions",
        "IIS-LegacyScripts",
        "IIS-LegacySnapIn",
        "Printing-Foundation-LPDPrintService",
        "Printing-Foundation-LPDPortMonitor",
        "Containers-HNS",
        "Containers-SDN",
        "Printing-Foundation-LPRPortMonitor",
        "SMB1Protocol-Deprecation"
    )

    function Get-FeatureNameList {
        $FeatureList = Get-WindowsOptionalFeature -Online | 
            Where-Object { $_.State -eq "Disabled" -and $_.FeatureName -notin $FeaturesToExclude } |
            Select-Object -ExpandProperty FeatureName
        return $FeatureList
    }

    # Check if the script is running as an administrator or the SYSTEM account
    if (-not (Test-IsElevated)) {
        Write-Host "[Error] This script must be run as an administrator or SYSTEM."
        exit 1
    }

    # Check if the device is a server
    if (Test-IsServer) {
        Write-Host "[Error] This device is a server. Optional Windows Features are only available on Windows 10, 11 and above."
        exit 1
    }

    if ($env:getFeatureNameList -and $env:getFeatureNameList -eq "true") {
        $GetFeatureNameList = $true
    }

    if ($env:featureNameToInstall -and $env:featureNameToInstall -ne "null") {
        $FeatureNameToInstall = $env:featureNameToInstall
    }

    if ($env:restartIfNeeded -and $env:restartIfNeeded -eq "true") {
        $RestartIfNeeded = $true
    }

    if ($env:installParentOrDefaultFeatures -and $env:installParentOrDefaultFeatures -eq "true") {
        $InstallParentOrDefaultFeatures = $true
    }


    if ($GetFeatureNameList -and $FeatureNameToInstall) {
        Write-Host "[Error] The Get Feature Name List and Feature Name To Install parameters cannot be used together."
        exit 1
    }

    # Check if the feature to enable is in the list of features to exclude and exit if it is
    if ($FeatureNameToInstall -and $FeaturesToExclude -contains $FeatureNameToInstall) {
        Write-Host "[Error] The feature $FeatureNameToInstall cannot be enabled due to high security risk."
        exit 1
    }

}

process {

    if ($GetFeatureNameList) {
        # List all available features that can be enabled
        Write-Host "[Info] The following features are available to enable:"
        Get-FeatureNameList | Sort-Object | ForEach-Object { Write-Host "$_" }
    }
    elseif ($FeatureNameToInstall) {

        # Check if the feature to enable is available
        if ($FeatureNameToInstall -notin (Get-FeatureNameList)) {
            Write-Host "[Error] The feature $FeatureNameToInstall is not available to enable. Either is not a valid feature or is already enabled."
            exit 1
        }

        # Enable the feature
        if ($(Set-Feature -FeatureName $FeatureNameToInstall)) {
            if ($RestartIfNeeded) {
                Start-Process shutdown.exe -ArgumentList "/r /t 60" -Wait -NoNewWindow
                Write-Host "[Info] The system will restart in 60 seconds."
            }
            else {
                Write-Host "[Info] Restart is needed."
            }
        }
        else {
            $exitCode = 0
            Get-WindowsOptionalFeature -Online | Where-Object { $_.FeatureName -eq $FeatureNameToInstall } | ForEach-Object {
                if ($_.State -eq "Enabled") {
                    Write-Host "[Info] The feature $FeatureNameToInstall is already enabled."
                }
                else {
                    Write-Host "[Error] The feature $FeatureNameToInstall could not be enabled."
                    $exitCode = 1
                }
            }
            exit $exitCode
        }
    }
}

end {
    
    
    
}

 

Detailed Breakdown

The script is organized into three primary blocks: BeginProcess, and End, following a typical PowerShell advanced function structure.

1. Environment Validation

  • Test-IsElevated checks if the script is run with administrative privileges.
  • Test-IsServer prevents execution on Windows Server environments, aligning with the intended use for Windows 10 and 11 workstations.

2. Parameter Handling

The script supports the following parameters:

  • -GetFeatureNameList: Lists all currently disabled but safe-to-enable optional features.
  • -FeatureNameToInstall: Specifies a feature to enable.
  • -RestartIfNeeded: Automatically restarts the system if required.
  • -InstallParentOrDefaultFeatures: Enables parent dependencies if needed.

3. Feature Management Logic

  • Uses Enable-WindowsOptionalFeature with robust error handling and warnings suppressed for cleaner logs.
  • Maintains a hardcoded exclusion list to avoid enabling deprecated or insecure features like SMBv1, Telnet, or IIS legacy components.

4. Environmental Overrides

The script accepts environment variables for parameters, making it suitable for integration into RMM tools or CI/CD pipelines.

Potential Use Cases

Consider an MSP managing 300 remote workstations for a legal firm. A legacy document management application requires Windows-Identity-Foundation enabled on each system. Rather than logging into each machine or deploying a Group Policy workaround (which lacks granularity), the MSP deploys this script via a NinjaOne custom script module. It checks each machine’s eligibility, skips servers, and safely enables the feature only if it’s not already present.

Comparisons

Traditional approaches, such as manual configuration through the Windows Features GUI or using DISM commands, fall short in automation and error handling. This PowerShell script offers:

  • Safety: Blocks insecure or legacy features.
  • Scalability: Easily deployable across many endpoints.
  • Clarity: Provides meaningful output for logging and diagnostics.

While Group Policy can configure some features, it lacks the conditional logic and environment validation embedded in this script.

Frequently Asked Questions (FAQs)

Can this script be used on Windows Server?

No. The script explicitly blocks execution on server OS types to prevent misuse.

What happens if the feature is already enabled?

The script will report that the feature is already enabled and take no further action.

How do I know which features are safe to enable?

Use the -GetFeatureNameList parameter. The script filters out deprecated or insecure features automatically.

Is a restart always required?

Not necessarily. The script checks the RestartNeeded property and only initiates a reboot if the -RestartIfNeeded flag is used.

Can I deploy this via an RMM tool?

Yes. The script supports environment variable overrides, making it ideal for use with NinjaOne or other automation platforms.

Implications

This script’s design highlights the intersection of automation and security. By excluding risky features and validating environments before execution, it prevents accidental exposure of legacy protocols like SMBv1—an often-overlooked security gap. In tightly controlled environments, scripts like this one form a first line of defense against configuration drift and vulnerability introduction.

Recommendations

When using this PowerShell script to enable optional Windows features:

  • Test in a lab environment before production deployment.
  • Use source control to version your scripts and changes.
  • Incorporate logging if integrating into larger automation workflows.
  • Avoid editing the exclusion list unless fully aware of the security implications.
  • Combine with monitoring tools to verify configuration states post-deployment.

Final Thoughts

PowerShell remains one of the most powerful tools for Windows system administration, and this script exemplifies its utility in real-world IT operations. For MSPs and IT departments leveraging NinjaOne, the integration possibilities are especially compelling. The script’s modularity and environment-aware design make it a perfect fit for NinjaOne’s scripting engine, enabling proactive feature management at scale with minimal manual overhead.

Whether you’re looking to enable optional Windows features on workstations using PowerShell or ensure secure and compliant configurations across hundreds of devices, this script offers both precision and peace of mind.

Next Steps

Building an efficient and effective IT team requires a centralized solution that acts as your core service delivery tool. NinjaOne enables IT teams to monitor, manage, secure, and support all their devices, wherever they are, without the need for complex on-premises infrastructure.

Learn more about NinjaOne Remote Script Deployment, check out a live tour, or start your free trial of the NinjaOne platform.

Categories:

You might also like

×

See NinjaOne in action!

By submitting this form, I accept NinjaOne's privacy policy.

NinjaOne Terms & Conditions

By clicking the “I Accept” button below, you indicate your acceptance of the following legal terms as well as our Terms of Use:

  • Ownership Rights: NinjaOne owns and will continue to own all right, title, and interest in and to the script (including the copyright). NinjaOne is giving you a limited license to use the script in accordance with these legal terms.
  • Use Limitation: You may only use the script for your legitimate personal or internal business purposes, and you may not share the script with another party.
  • Republication Prohibition: Under no circumstances are you permitted to re-publish the script in any script library belonging to or under the control of any other software provider.
  • Warranty Disclaimer: The script is provided “as is” and “as available”, without warranty of any kind. NinjaOne makes no promise or guarantee that the script will be free from defects or that it will meet your specific needs or expectations.
  • Assumption of Risk: Your use of the script is at your own risk. You acknowledge that there are certain inherent risks in using the script, and you understand and assume each of those risks.
  • Waiver and Release: You will not hold NinjaOne responsible for any adverse or unintended consequences resulting from your use of the script, and you waive any legal or equitable rights or remedies you may have against NinjaOne relating to your use of the script.
  • EULA: If you are a NinjaOne customer, your use of the script is subject to the End User License Agreement applicable to you (EULA).