Providers/AD/Private/Install-ADTestRsatFeature.ps1

function Install-ADTestRsatFeature {
    <#
    .SYNOPSIS
        Installs the RSAT feature that provides one of the modules the AD provider needs

    .DESCRIPTION
        Only called when Connect-ADEnvironment was given -InstallRsat and the import failed.
        Installing RSAT is a machine-wide change that can reboot-pend, so it is never automatic.

        Two mechanisms, because Windows has two. A workstation gets the feature as an optional
        capability through Add-WindowsCapability; a server gets it as a Windows feature through
        Install-WindowsFeature, which lives in the ServerManager module and is absent on client
        SKUs. The capability route is tried first and the server route only if ServerManager is
        actually present, so neither path depends on guessing the SKU correctly.

        A domain controller already has both modules, so in practice this runs on a member
        server or an admin workstation.

    .PARAMETER Module
        Which module is missing: ActiveDirectory or GroupPolicy.

    .OUTPUTS
        System.Boolean, true when the feature is installed and the module should now import.

    .EXAMPLE
        PS> Install-ADTestRsatFeature -Module ActiveDirectory

        DESCRIPTION: Installs RSAT's AD DS tools
        OUTPUT: True on success
        USE CASE: Called by Connect-ADEnvironment -InstallRsat

    .NOTES
        Author: Jeffrey Stuhr
        Blog: https://www.techbyjeff.net
        LinkedIn: https://www.linkedin.com/in/jeffrey-stuhr-034214aa/
    #>


    [CmdletBinding(SupportsShouldProcess, ConfirmImpact = 'High')]
    [OutputType([bool])]
    param(
        [Parameter(Mandatory = $true)]
        [ValidateSet('ActiveDirectory', 'GroupPolicy')]
        [string]$Module
    )

    $capability = switch ($Module) {
        'ActiveDirectory' { 'Rsat.ActiveDirectory.DS-LDS.Tools' }
        'GroupPolicy' { 'Rsat.GroupPolicy.Management.Tools' }
    }

    $feature = switch ($Module) {
        'ActiveDirectory' { 'RSAT-AD-PowerShell' }
        'GroupPolicy' { 'GPMC' }
    }

    if (-not $PSCmdlet.ShouldProcess($env:COMPUTERNAME, "Install the RSAT feature providing $Module")) {
        return $false
    }

    if (Get-Command -Name Add-WindowsCapability -ErrorAction SilentlyContinue) {
        try {
            # -Name matches on a version-suffixed capability name, so the installed one is found
            # by prefix rather than by a version this module would have to keep current.
            $available = @(Get-WindowsCapability -Online -Name "$capability*" -ErrorAction Stop)
            $target = $available | Where-Object { $_.State -ne 'Installed' } | Select-Object -First 1

            if (-not $target) {
                Write-Verbose "$capability reports as already installed"
                return $true
            }

            $null = Add-WindowsCapability -Online -Name $target.Name -ErrorAction Stop
            Write-Verbose "Installed capability $($target.Name)"
            return $true
        }
        catch {
            Write-Verbose "Add-WindowsCapability could not install $capability : $($_.Exception.Message)"
        }
    }

    # ServerManager is absent on client SKUs, so its absence means this was never the right
    # route rather than that something went wrong.
    if (Get-Module -ListAvailable -Name ServerManager) {
        try {
            Import-Module ServerManager -ErrorAction Stop -Verbose:$false
            $null = Install-WindowsFeature -Name $feature -IncludeManagementTools -ErrorAction Stop
            Write-Verbose "Installed Windows feature $feature"
            return $true
        }
        catch {
            Write-Verbose "Install-WindowsFeature could not install $feature : $($_.Exception.Message)"
        }
    }

    return $false
}