Private/Get-WinClatInstanceScript.ps1
|
Function Get-WinClatInstanceScript { <# .SYNOPSIS Returns the self-contained script block that Get-WinClatInstance runs on each target. .DESCRIPTION The script block takes one positional argument, an array of InterfaceAlias patterns (empty for all adapters), and follows the execution order in DESIGN.md, section 4.4: 1. Feature check: 'clat' in the 'netsh interface ?' sub-context list. If absent, returns one object with Status NotSupported on Windows Server or FeatureNotAvailable on a client (Get-WinClatFeatureFailure). 2. Elevation check on the current token. If not elevated, returns one ElevationRequired object. 3. Enumerates up adapters with Get-NetAdapter, drops adapters without IPv6 (ms_tcpip6) bound, filters by InterfaceAlias (wildcards supported), and runs 'netsh interface clat show instance interface=<ifIndex>' for each (section 4.7). If the IPv6 bindings cannot be read, no adapters are dropped and the reason is written to the verbose stream. 4. Classifies a non-zero exit code for an adapter with Get-WinClatNetshFailure. Other adapters are still reported. For an enabled instance, TriggerSource comes from the newest event 1106 for the adapter's LUID in the IPxlatCfg event log (Get-WinClatTriggerSource), and Mode is Manual when TriggerSource is Manual and Automatic otherwise. Windows keeps a manually enabled instance running when autoenable is enabled again, so autoenable alone does not show it. When the instance is disabled or no event is found, Mode is derived from the active autoenable value read with 'show global' in the same session: Automatic when enabled, Manual when disabled, $null when it cannot be read (section 6). When no up adapter with IPv6 enabled matches, returns one Success object with no interface data and a message, and no error (section 7.2). Get-WinClatInstance and the unit tests both use this function, so the tests exercise the script block that is sent remotely. .INPUTS None. This function does not accept pipeline input. .OUTPUTS System.Management.Automation.ScriptBlock. .EXAMPLE & (Get-WinClatInstanceScript) @('Wi-Fi') Returns the WinCLAT state of the Wi-Fi adapter on the local computer. .LINK https://learn.microsoft.com/powershell/module/netadapter/get-netadapter .NOTES Version: 1.0.0 Creation Date: October 28, 2026 Last Updated: October 28, 2026 Author: Richard Hicks Organization: Richard M. Hicks Consulting, Inc. Contact: rich@richardhicks.com Website: https://www.richardhicks.com/ #> [CmdletBinding()] [OutputType([scriptblock])] Param () $HelperName = @( 'ConvertFrom-WinClatNetshOutput' 'ConvertTo-WinClatTimeSpan' 'ConvertTo-WinClatInstance' 'Get-WinClatNetshFailure' 'Get-WinClatTriggerSource' 'Test-WinClatElevation' 'Get-WinClatFeatureFailure' 'Test-WinClatFeature' ) $Body = { Param ($InterfaceAlias) # Flatten, so the patterns work however the array arrives (Invoke-Command, a local runspace, or a direct call). $InterfaceAlias = @($InterfaceAlias | ForEach-Object -Process { $_ } | Where-Object -FilterScript { $_ }) $Help = @(netsh.exe interface '?') If (-not (Test-WinClatFeature -InputObject $Help)) { # NotSupported on Windows Server, FeatureNotAvailable on a client that has not received the feature. $Missing = Get-WinClatFeatureFailure $Failure = @{ ComputerName = $env:COMPUTERNAME Status = $Missing.Status CollectedAtUtc = (Get-Date).ToUniversalTime() Message = $Missing.Message } ConvertTo-WinClatInstance @Failure Return } If (-not (Test-WinClatElevation)) { $Failure = @{ ComputerName = $env:COMPUTERNAME Status = 'ElevationRequired' CollectedAtUtc = (Get-Date).ToUniversalTime() Message = 'Reading WinCLAT instance state requires an elevated session. Run PowerShell as administrator, or connect with an account that is a local administrator on the target. Get-WinClatConfiguration does not require elevation.' } ConvertTo-WinClatInstance @Failure Return } $Adapters = @(Get-NetAdapter | Where-Object -FilterScript { $_.Status -eq 'Up' } | Sort-Object -Property ifIndex) # An adapter without IPv6 bound, such as a physical NIC attached to a Hyper-V switch, can never run CLAT. Try { $IPv6Bound = @(Get-NetAdapterBinding -ComponentID 'ms_tcpip6' -ErrorAction Stop | Where-Object -FilterScript { $_.Enabled } | ForEach-Object -Process { $_.Name }) $Adapters = @($Adapters | Where-Object -FilterScript { $IPv6Bound -contains $_.Name }) } Catch { Write-Verbose -Message "IPv6 bindings could not be read, so no adapters were excluded: $($_.Exception.Message)" } If ($InterfaceAlias.Count -gt 0) { $Adapters = @($Adapters | Where-Object -FilterScript { $Alias = $_.Name @($InterfaceAlias | Where-Object -FilterScript { $Alias -like $_ }).Count -gt 0 }) } If ($Adapters.Count -eq 0) { $Message = 'No up network adapter with IPv6 enabled was found.' If ($InterfaceAlias.Count -gt 0) { $Message = "No up network adapter with IPv6 enabled matches InterfaceAlias '$($InterfaceAlias -join "', '")'." } ConvertTo-WinClatInstance -ComputerName $env:COMPUTERNAME -Status 'Success' -CollectedAtUtc (Get-Date).ToUniversalTime() -Message $Message Return } # Mode falls back to the active autoenable value; netsh does not report how an instance was enabled. $Mode = $null $GlobalOutput = @(netsh.exe interface clat show global) If ($LASTEXITCODE -eq 0) { $GlobalSettings = @(ConvertFrom-WinClatNetshOutput -InputObject $GlobalOutput)[0] If ($GlobalSettings -and $GlobalSettings.PSObject.Properties['AutoEnable'] -and $GlobalSettings.AutoEnable -is [bool]) { $Mode = 'Manual' If ($GlobalSettings.AutoEnable) { $Mode = 'Automatic' } } } # Read only when an enabled instance is found, so computers without active CLAT do not query the log. $TriggerSources = $null ForEach ($Adapter in $Adapters) { $Output = @(netsh.exe interface clat show instance "interface=$($Adapter.ifIndex)") $ExitCode = $LASTEXITCODE $Result = @{ ComputerName = $env:COMPUTERNAME InterfaceAlias = $Adapter.Name InterfaceIndex = $Adapter.ifIndex Mode = $Mode CollectedAtUtc = (Get-Date).ToUniversalTime() } If ($ExitCode -ne 0) { $Classified = Get-WinClatNetshFailure -InputObject $Output -ExitCode $ExitCode ConvertTo-WinClatInstance @Result -Status $Classified.Status -Message $Classified.Message Continue } $Settings = @(ConvertFrom-WinClatNetshOutput -InputObject $Output)[0] If (-not $Settings) { ConvertTo-WinClatInstance @Result -Status 'Failed' -Message "netsh.exe returned no instance data: $((@($Output) -join ' ').Trim())" Continue } # Windows keeps a manually enabled instance when autoenable is enabled again, so the event decides when it exists. If ($Settings.Enabled -eq $true -and $Adapter.NetLuid) { If ($null -eq $TriggerSources) { $TriggerSources = Get-WinClatTriggerSource } $Trigger = $TriggerSources[[string]$Adapter.NetLuid] If ($Trigger) { $Result['TriggerSource'] = $Trigger $Result['Mode'] = 'Automatic' If ($Trigger -eq 'Manual') { $Result['Mode'] = 'Manual' } } } ConvertTo-WinClatInstance @Result -Status 'Success' -Settings $Settings } } Get-WinClatRemoteScript -HelperName $HelperName -ScriptBlock $Body } |