Public/Ensure-RSAT.ps1
function Ensure-RSAT { <# .SYNOPSIS Ensures RSAT tools are installed. Installs if missing. .DESCRIPTION On Windows 10 1809+ / Windows 11 (client): installs RSAT via Features on Demand (WindowsCapabilities). On Windows Server: installs RSAT via Windows Features (Install-WindowsFeature). By default, this ensures the Active Directory RSAT tools (for the 'ActiveDirectory' PS module). Use -All to install all RSAT components available on the platform. Returns $true if the requested RSAT tools are available after the operation, otherwise $false. .PARAMETER All Install all RSAT components (instead of just AD DS/AD LDS tools). .PARAMETER BypassWSUS On client OS only: temporarily disables WSUS usage for the install (common fix for 0x800f0954), attempts the install from Windows Update, then restores the original setting. .PARAMETER MinADModuleVersion Minimum version of the 'ActiveDirectory' PowerShell module to consider as available. Default: 1.0.0.0 .EXAMPLE Ensure-RSAT # Ensures Active Directory RSAT tools are present. Installs if needed. .EXAMPLE Ensure-RSAT -All -Verbose # Installs all RSAT tools (client: all Rsat.* capabilities; server: all RSAT-* features). .EXAMPLE Ensure-RSAT -BypassWSUS # Temporarily bypass WSUS to fetch RSAT FODs from Windows Update (client OS only). .NOTES Requires administrative privileges. #> [CmdletBinding(SupportsShouldProcess = $true)] [OutputType([bool])] param( [switch]$All, [switch]$BypassWSUS, [Version]$MinADModuleVersion = '1.0.0.0' ) # --- Helpers --- function Test-IsAdmin { try { $id = [Security.Principal.WindowsIdentity]::GetCurrent() $pr = New-Object Security.Principal.WindowsPrincipal($id) return $pr.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator) } catch { return $false } } function Test-ADModuleAvailable { param([Version]$MinVersion = '1.0.0.0') $m = Get-Module -ListAvailable -Name ActiveDirectory -ErrorAction SilentlyContinue | Sort-Object Version -Descending | Select-Object -First 1 return ($m -and $m.Version -ge $MinVersion) } # --- Preflight --- if (-not (Test-IsAdmin)) { Write-Error "Ensure-RSAT must be run from an elevated (Administrator) PowerShell session." return $false } # Already good? if (-not $All) { if (Test-ADModuleAvailable -MinVersion $MinADModuleVersion) { Write-Verbose "ActiveDirectory module already available at required version." return $true } } $os = Get-CimInstance Win32_OperatingSystem $isServer = ($os.ProductType -ne 1) # 1 = Workstation, 2/3 = Domain Controller/Server $build = [int]$os.BuildNumber # For client installs, we may need to bypass WSUS to avoid 0x800f0954. $wuRegPath = 'HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate\AU' $originalUseWUServer = $null $changedWSUS = $false try { if ($isServer) { # ------------------------- # Windows Server (2016/2019/2022) # ------------------------- Import-Module ServerManager -ErrorAction SilentlyContinue if ($All) { $rsatFeatures = Get-WindowsFeature -Name RSAT* -ErrorAction SilentlyContinue | Where-Object { $_.InstallState -ne 'Installed' } | Select-Object -ExpandProperty Name if ($rsatFeatures.Count -eq 0) { Write-Verbose "All RSAT features already installed." } else { if ($PSCmdlet.ShouldProcess("Server RSAT features", "Install $($rsatFeatures -join ', ')")) { Install-WindowsFeature -Name $rsatFeatures -IncludeAllSubFeature -ErrorAction Stop | Out-Null } } # Validate: if All, success = AD module available OR at least some RSAT features installed return (Test-ADModuleAvailable -MinVersion $MinADModuleVersion) -or ((Get-WindowsFeature -Name RSAT* | Where-Object InstallState -eq 'Installed').Count -gt 0) } else { # Just the AD PowerShell module / tools $target = @('RSAT-AD-PowerShell','RSAT-AD-AdminCenter','RSAT-AD-Tools') | Where-Object { (Get-WindowsFeature -Name $_ -ErrorAction SilentlyContinue).InstallState -ne 'Installed' } if ($target) { if ($PSCmdlet.ShouldProcess("Active Directory RSAT", "Install $($target -join ', ')")) { Install-WindowsFeature -Name $target -IncludeAllSubFeature -ErrorAction Stop | Out-Null } } else { Write-Verbose "AD RSAT features already installed." } return (Test-ADModuleAvailable -MinVersion $MinADModuleVersion) } } else { # ------------------------- # Windows Client (Win10/11) # ------------------------- if ($build -lt 17763) { Write-Warning "This client OS is pre-1809 (build $build). RSAT is not a Feature on Demand here. Please download and install the RSAT package from Microsoft manually." return (Test-ADModuleAvailable -MinVersion $MinADModuleVersion) } # Work around WSUS if requested if ($BypassWSUS) { try { if (Test-Path $wuRegPath) { $originalUseWUServer = (Get-ItemProperty -Path $wuRegPath -Name UseWUServer -ErrorAction SilentlyContinue).UseWUServer } New-Item -Path $wuRegPath -Force | Out-Null Set-ItemProperty -Path $wuRegPath -Name UseWUServer -Value 0 -Type DWord Restart-Service -Name wuauserv -Force -ErrorAction SilentlyContinue $changedWSUS = $true Write-Verbose "Temporarily disabled WSUS for Features on Demand installation." } catch { Write-Warning "Could not modify WSUS setting: $($_.Exception.Message)" } } # Decide which capabilities to install if ($All) { $caps = Get-WindowsCapability -Online | Where-Object { $_.Name -like 'Rsat.*' -and $_.State -ne 'Installed' } if (-not $caps) { Write-Verbose "All RSAT capabilities already installed." } else { foreach ($c in $caps) { if ($PSCmdlet.ShouldProcess($c.Name, "Add-WindowsCapability")) { try { Add-WindowsCapability -Online -Name $c.Name -ErrorAction Stop | Out-Null Write-Verbose "Installed: $($c.Name)" } catch { Write-Warning "Failed: $($c.Name) - $($_.Exception.Message)" } } } } # Validate: AD module available OR all Rsat.* report Installed return (Test-ADModuleAvailable -MinVersion $MinADModuleVersion) -or -not (Get-WindowsCapability -Online | Where-Object { $_.Name -like 'Rsat.*' -and $_.State -ne 'Installed' }) } else { # Ensure only AD DS/AD LDS tools (contains the AD module) $adCapName = 'Rsat.ActiveDirectory.DS-LDS.Tools~~~~0.0.1.0' $state = (Get-WindowsCapability -Online -Name $adCapName -ErrorAction SilentlyContinue).State if ($state -ne 'Installed') { if ($PSCmdlet.ShouldProcess($adCapName, "Add-WindowsCapability")) { Add-WindowsCapability -Online -Name $adCapName -ErrorAction Stop | Out-Null Write-Verbose "Installed capability: $adCapName" } } else { Write-Verbose "Capability already installed: $adCapName" } return (Test-ADModuleAvailable -MinVersion $MinADModuleVersion) } } } catch { Write-Error "Ensure-RSAT failed: $($_.Exception.Message)" return $false } finally { # Restore WSUS setting if we changed it if ($changedWSUS) { try { if ($null -eq $originalUseWUServer) { Remove-ItemProperty -Path $wuRegPath -Name UseWUServer -ErrorAction SilentlyContinue } else { Set-ItemProperty -Path $wuRegPath -Name UseWUServer -Value $originalUseWUServer -Type DWord -ErrorAction SilentlyContinue } Restart-Service -Name wuauserv -Force -ErrorAction SilentlyContinue Write-Verbose "Restored WSUS configuration." } catch { Write-Warning "Failed to restore WSUS config: $($_.Exception.Message)" } } } } |