WifiConnectionAPI.psm1

<#
 .Synopsis
  Wifi
 .Description
  Manage Wifi connections
 .Parameter Get-RadioState
 .Parameter Set-RadioState
 .Parameter Get-WifiDeviceInformation
 .Parameter Get-WiFiAvailableAdapter
 .Parameter Get-WifiConnectionProfile
 .Parameter Remove-WifiConnectionProfile
 .Parameter Get-WifiCurrentConection
 .Parameter Get-WifiAvailableNetwork
 .Parameter Search-WifiNetworks
 .Parameter Disconnect-WifiNetwork
 .Parameter Connect-WiFiNetwork
#>


ImportSystemModules

Add-Type -AssemblyName System.Runtime.WindowsRuntime

# Zoek de juiste overloads voor zowel Operations (met resultaat) als Actions (zonder resultaat)
$asTaskGeneric = [System.WindowsRuntimeSystemExtensions].GetMethods() | Where-Object { 
  $_.Name -eq 'AsTask' -and $_.GetParameters().Count -eq 1 
}

$asTaskOperation = $asTaskGeneric | Where-Object { $_.GetParameters()[0].ParameterType.Name -eq 'IAsyncOperation`1' }
$asTaskAction = $asTaskGeneric | Where-Object { $_.GetParameters()[0].ParameterType.Name -eq 'IAsyncAction' }

Function Await($WinRtTask, $ResultType, [int]$TimeoutMs = 10000) {
  # Forceer de timeout tussen 1000 en 60000 ms
  if ($TimeoutMs -lt 1000) { $TimeoutMs = 1000 }
  if ($TimeoutMs -gt 60000) { $TimeoutMs = 60000 }
  if ($ResultType) {
    $asTask = $asTaskOperation.MakeGenericMethod($ResultType)
    $netTask = $asTask.Invoke($null, @($WinRtTask))
  }
  else {
    $netTask = $asTaskAction.Invoke($null, @($WinRtTask))
  }
  # .Wait() geeft True als het lukt, False bij timeout
  if ($netTask.Wait($TimeoutMs)) {
    # Alleen Result teruggeven als het een Task<T> is (met ResultType)
    if ($ResultType) { return $netTask.Result }
    return $true
  }
  else {
    Write-Error "De WinRT terminated afther $($TimeoutMs)ms."
    return $null
  }
}

[Windows.Devices.Radios.Radio, Windows.System.Devices, ContentType = WindowsRuntime] | Out-Null
[Windows.Devices.Radios.RadioState, Windows.System.Devices, ContentType = WindowsRuntime] | Out-Null
[Windows.Devices.Radios.RadioAccessStatus, Windows.System.Devices, ContentType = WindowsRuntime] | Out-Null

Function Get-RadioState {
  <#
.SYNOPSIS
  Retrieves all available radio adapters (WiFi, Bluetooth, Mobile) and displays their current status.
.DESCRIPTION
  This function uses the Windows Runtime (WinRT) API to access the system's radio hardware directly.
  The result is a list of Radio objects that can be piped directly into Set-RadioState.
.EXAMPLE
  Get-RadioState
  Displays all radios and whether they are 'On' or 'Off'.
.EXAMPLE
  Get-RadioState | Where-Object Kind -eq 'WiFi' | Set-RadioState -SetState Off
  Specifically searches for the WiFi radio and turns it off.
.OUTPUTS
  Windows.Devices.Radios.Radio
#>

  [outputType([Windows.Devices.Radios.Radio])]
  param(
    [int]$TimeoutMS = 10000
  )
  Process {
    $accessStatus = Await ([Windows.Devices.Radios.Radio]::RequestAccessAsync()) ([Windows.Devices.Radios.RadioAccessStatus])
    if ($accessStatus -eq "Allowed") {
      Await -WinRtTask ([Windows.Devices.Radios.Radio]::GetRadiosAsync()) -ResultType ([System.Collections.Generic.IReadOnlyList[Windows.Devices.Radios.Radio]]) -TimeoutMs $TimeoutMS
    }
    else {
      Write-Warning "No Access to radio hardware Status: $accessStatus" 
    }
  }
}

Function Set-RadioState {
  <#
.SYNOPSIS
  Turns a specific radio on or off.
.DESCRIPTION
  Accepts Radio objects (typically from the pipeline of Get-RadioState) and
  attempts to change the physical status to 'On' or 'Off'.
.PARAMETER SetState
  The desired target state. Use 'On' or 'Off'. (Supports autocomplete).
.PARAMETER Radios
  The Radio object to be modified. Accepts input via the pipeline.
.EXAMPLE
  Get-RadioState | Set-RadioState -SetState Off
  Get-RadioState | Where-Object Name -eq 'Wi-Fi' | Set-RadioState -SetState On
  Attempts to turn off all radios on the system.
.NOTES
  Some adapters (such as Mobile Broadband) may refuse the status change
  if they are managed elsewhere by the system. In such cases, the function will
  issue a warning rather than crashing.
#>

  param(
    [Parameter(Mandatory = $true)]$SetState,
    [Parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)][Alias("Radio")][Windows.Devices.Radios.Radio[]]$Radios,
    [int]$TimeoutMs = 10000
  )
  process {
    foreach ($R in $Radios) {
      $Task = $R.SetStateAsync($SetState)
      $ResultType = [Windows.Devices.Radios.RadioAccessStatus]
      $Status = Await -WinRtTask $Task -ResultType $ResultType -TimeoutMs $TimeoutMs
      if ($Status -ne 'Allowed') {
        Write-Warning "Radio $($R.Name) access status: $Status"
      }
    }
  }
}

[Windows.Devices.WiFi.WiFiAdapter, Windows.Devices.Wifi, ContentType = WindowsRunTime] | Out-Null
[Windows.Devices.WiFi.WiFiReconnectionKind, Windows.DEvices.Wifi, ContentType = WindowsRuntime] | Out-Null
[Windows.Devices.Enumeration.DeviceInformation, Windows.Devices.Enumeration, ContentType = WindowsRunTime] | Out-Null
[Windows.Security.Credentials.PasswordCredential, Windows.Security.Credentials, ContentType = WindowsRunTime] | Out-Null
[Windows.Networking.Connectivity.NetworkInformation, Windows.Networking.Connectivity, Contenttype = WindowsRuntime] | Out-Null

Function Get-WifiDeviceInformation {
  [OutputType([Windows.Devices.Enumeration.DeviceInformationCollection])]
  Param(
    [int]$TimeoutMs = 10000
  )
  Process {
    Await -WinRtTask ([Windows.Devices.Enumeration.DeviceInformation]::FindAllAsync([Windows.Devices.WiFi.WiFiAdapter]::GetDeviceSelector())) -ResultType ([Windows.Devices.Enumeration.DeviceInformationCollection]) -TimeoutMs $TimeoutMs
  }
}

Function Get-WiFiAvailableAdapter {
  [outputtype([Windows.Devices.WiFi.WiFiAdapter])]
  param(
    [int]$TimeoutMs = 10000
  )
  Process {
    Await -WinRtTask ([Windows.Devices.WiFi.WiFiAdapter]::FindAllAdaptersAsync()) -ResultType ([System.Collections.Generic.IReadOnlyList[Windows.Devices.WiFi.WiFiAdapter]]) -TimeoutMs $TimeoutMs
  }
}

Function Search-WifiNetworks {
  [OutputType([Windows.Devices.Wifi.WifiAvailableNetwork])]
  param(
    # We laten het type vrij of gebruiken de WinRT klasse
    [Parameter(ValueFromPipeline = $true)]
    [Windows.Devices.WiFi.WiFiAdapter]$WiFiAdapter,
    [int]$TimeoutMs = 10000
  )
  begin {
    # De 'Smart Load' logica
    if (-not $WiFiAdapter) {
      # Gebruik je bestaande stabiele functie als fallback
      $WiFiAdapter = Get-WiFiAvailableAdapter -TimeoutMs $TimeoutMs
    }
  }
  Process {
    Write-Progress -Activity "WiFi" -Status "Scanning WiFi Networks"
    [Void](Await -WinRtTask ($WiFiAdapter.ScanAsync()) -ResultType ($null) -TimeoutMs $TimeoutMs )
    Write-Progress -Activity "WiFi" -Completed
    $script:WiFiLastScanTime = Get-Date
    return $WifiAdapter.NetworkReport.AvAilableNetworks
  }
}

Function Get-WifiConnectionProfile {
  [OutputType([Windows.Networking.Connectivity.ConnectionProfile[]])]
  Param()
  Process {
    try {
      $Profiles = [Windows.Networking.Connectivity.NetworkInformation]::GetConnectionProfiles()
      $WifiProfiles = $Profiles | Where-Object { $_.IsWlanConnectionProfile }
      if ($null -eq $WifiProfiles) {
        Write-Verbose "No WiFi connection profiles found on this system."
      }
      return $WifiProfiles
    }
    catch {
      Write-Error "Failed to retrieve connection profiles: $($_.Exception.Message)"
    }
  }
}

Function Remove-WifiConnectionProfile {
  param(
    [parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)]
    [Alias("Profile")]
    [Windows.Networking.Connectivity.ConnectionProfile[]]$Profiles,
    [int]$TimeoutMS = 10000 # Nu netjes als laatste parameter
  )
  process {
    foreach ($P in $Profiles) {
      if ($P.CanDelete) {
        Write-Host ("Removing Profile {0}" -f $P.ProfileName) -ForegroundColor Cyan
        # De WinRT call klaarzetten
        $task = $P.TryDeleteAsync()
        $resultType = [Windows.Networking.Connectivity.ConnectionProfileDeleteStatus]
        # Await aanroepen met de gekozen timeout
        $status = Await -WinRtTask $task -ResultType $resultType -TimeoutMs $TimeoutMS
        if ($status -eq "Success") {
          Write-Host "Profile Succesfull removed" -ForegroundColor Green
        }
        elseif ($null -eq $status) {
          Write-Warning "Timeout reached for removing profile: $($P.ProfileName)."
        }
        else {
          Write-Error "Error drunig remove: $($status)"
        }
      }
      else {
        Write-Warning ("Cant Remove Profile {0}" -f $P.ProfileName)
      }
    }
  } 
}

Function Get-WifiCurrentConnection {
  [OutputType([Windows.Networking.Connectivity.ConnectionProfile[]])]
  Param(
    [Parameter(Position = 0)]
    [int[]]$Index,
    [Parameter(Position = 1)]
    [int]$TimeoutMS = 10000
  )
  Process {
    try {
      $Adapters = Get-WiFiAvailableAdapter
      if ($null -eq $Adapters -or $Adapters.Count -eq 0) {
        Write-Warning "No WiFi adapters found to check connections."
        return $null
      }
      # Determine which adapters to target based on Index
      $Targets = if ($PSBoundParameters.ContainsKey('Index')) {
        foreach ($i in $Index) {
          if ($i -ge 0 -and $i -lt $Adapters.Count) { $Adapters[$i] }
          else { Write-Warning "Index $i skipped: does not exist (range: 0 to $($Adapters.Count - 1))." }
        }
      }
      else {
        $Adapters # Default: Check all
      }
      $Results = [System.Collections.Generic.List[Object]]::new()
      foreach ($Adapter in $Targets) {
        $Task = $Adapter.NetworkAdapter.GetConnectedProfileAsync()
        $ResultType = [Windows.Networking.Connectivity.ConnectionProfile]
        # Await the async task
        $CurrentWifiProfile = Await -WinRtTask $Task -ResultType $ResultType -TimeoutMs $TimeoutMS
        if ($null -ne $CurrentWifiProfile) {
          $Results.Add($CurrentWifiProfile)
        }
      }
      if ($Results.Count -eq 0) {
        Write-Verbose "No active WiFi connection found on the selected adapter(s)."
        return $null
      }
      return $Results.ToArray()
    }
    catch {
      Write-Error "Failed to retrieve current connection: $($_.Exception.Message)"
      return $null
    }
  }
}

Function Disconnect-WifiNetwork {
  param(
    [Parameter(Mandatory = $false, Position = 0)]
    [int[]]$Index
  )
  Process {
    $Adapters = Get-WiFiAvailableAdapter
    if ($null -eq $Adapters -or $Adapters.Count -eq 0) {
      Write-Warning "No WiFi adapters found to disconnect."
      return
    }
    $Targets = if ($PSBoundParameters.ContainsKey('Index')) {
      foreach ($i in $Index) {
        if ($i -ge 0 -and $i -lt $Adapters.Count) { $Adapters[$i] }
        else { Write-Warning "Index $i skipped: does not exist (range: 0 to $($Adapters.Count - 1))." }
      }
    }
    else {
      $Adapters
    }
    foreach ($A in $Targets) {
      try {
        $A.Disconnect()
        Write-Host "Disconnected adapter: $($A.NetworkAdapter.NetworkAdapterId)" -ForegroundColor Green
      }
      catch {
        Write-Error "Failed to disconnect: $($_.Exception.Message)"
      }
    }
  }
}

Function Get-WifiAvailableNetworks {
  [CmdletBinding()] # CRUCIAAL: Dit activeert de Write-Debug/Verbose functionaliteit
  [OutputType([Windows.Devices.Wifi.WifiAvailableNetwork])]
  param(
    [String]$DeviceName
  )
  Process {
    if ($PSBoundParameters.ContainsKey('Debug')) { $DebugPreference = [System.Management.Automation.ActionPreference]::Continue }
    #$DeviceName = $PSBoundParameters['DeviceName']
    # Zoek de adapter op basis van de InterfaceDescription (de naam uit de completer)
    if (-not [string]::IsNullOrEmpty(($DeviceName)) ) {
      $adapter = Get-NetAdapter | Where-Object { $_.InterfaceDescription -eq $DeviceName }
      if ([string]::IsNullOrEmpty(($adapter)) ) {
        Write-Error "Adapter '$DeviceName' niet gevonden."
        return # Stop de uitvoering voor dit item
      }
      else {
        # Haal de GUID (InstanceID) op
        $adapterGuid = $adapter.InstanceID.Trim('{').Trim('}')
        Write-Debug "GUID gevonden voor $DeviceName : $adapterGuid"    
      }
    }
    if ($PSBoundParameters.ContainsKey('Debug')) { $DebugPreference = [System.Management.Automation.ActionPreference]::Continue }
    Write-Debug "GUID:'$adapterGuid'"
    Write-Debug "devicename:'$DeviceName'"
    if ([string]::IsNullOrEmpty($DeviceName)) {
      Write-Debug "No Device Selected return all"
      return (Get-WiFiAvailableAdapter).NetworkReport.AvailableNetworks
    }
    elseif (-not [string]::IsNullOrEmpty($adapterGuid)) {
      write-debug "A Device has been Selected return specifick"
      return (Get-WiFiAvailableAdapter | Where-Object { $_.NetworkAdapter.NetworkAdapterID -match $adapterGuid }).NetworkReport.AvailableNetworks
    }
    else {
      Write-Debug "The Device Name was wrong fix it"
    }
  }
} 

Function Connect-WiFiNetwork {
  <#
.SYNOPSIS
  Connects one, multiple, or all WiFi adapters to a specific network (SSID).
.DESCRIPTION
  This function uses the WinRT API to connect. It supports simultaneously targeting specific adapters by their index. If a network is already known to Windows (profile exists), the password is not required.
.PARAMETER Ssid
  Mandatory The name of the WiFi network. Use the TAB key for suggestions of available nearby networks.
.PARAMETER Password
  Optional The password for the network. This is a dynamic parameter that uses SecureString (unless -AsPlainText is specified). Not required if a stored profile already exists.
.PARAMETER Index
  Optional The index of the adapter(s) (e.g., 0, 1). Leave this blank to attempt the connection on ALL available WiFi adapters simultaneously.
.PARAMETER User
  Optional username for enterprise networks (802.1X).
.PARAMETER Bssid
  Optional Force connection to a specific Access Point based on the MAC address.
.EXAMPLE
  Connect-WiFiNetwork -Ssid "MyHomeNetwork" -Index 0
  Connects the first adapter to the specified network.
.EXAMPLE
  Connect-WiFiNetwork -Ssid "Open_WiFi"
  Attempts to connect all WiFi adapters on the system to an open network.
.EXAMPLE
  Connect-WiFiNetwork -Ssid "Company_WiFi" -Password (Read-Host -AsSecureString) -Index 0, 2
  Connects adapter 0 and 2 to a secured network with manual password input.
.NOTES
  The function checks each adapter to ensure the SSID is visible before initiating the connection.
#>

  [CmdletBinding()]
  param(
    [Parameter(Mandatory = $true)]$Ssid, 
    [string]$Bssid,
    [string]$User,
    [Switch]$AsPlainText,
    [Switch]$Hidden,
    [int[]]$Index,
    [Int]$TimeoutMS = 10000,
    [Windows.Devices.WiFi.WiFiReconnectionKind]$WifiReconnectionKind = [Windows.Devices.WiFi.WiFiReconnectionKind]::Automatic
  )
  DynamicParam {
    # Kies het type op basis van de switch
    $parameterType = if ($AsPlainText) { [string] } else { [securestring] }
    # Maak de collectie voor attributen (zoals Mandatory)
    $attributeCollection = New-Object System.Collections.ObjectModel.Collection[System.Attribute]
    $attribute = New-Object System.Management.Automation.ParameterAttribute
    $attribute.Mandatory = $false
    $attributeCollection.Add($attribute)
    # Definieer de parameter 'Password' runtime
    $dynParam = New-Object System.Management.Automation.RuntimeDefinedParameter('Password', $parameterType, $attributeCollection)
    # Voeg toe aan de dictionary die PowerShell terug verwacht
    $paramDictionary = New-Object System.Management.Automation.RuntimeDefinedParameterDictionary
    $paramDictionary.Add('Password', $dynParam)
    return $paramDictionary
  }
  begin {
    # Wachtwoord-check (Zoals we eerder bespraken: PlainText vs SecureString)
    if ($Password -and $Password -isnot [System.Security.SecureString] -and -not $AsPlainText) {
      throw [System.Security.SecurityException]::new("Password is plainText. Use the switch -AsPlainText or use SecureString in.")
    }
    # 1. Directe validatie van parameters
    if ($Hidden -and -not $Bssid) {
      throw "Error: when -Hidden is used -Bssid is Mandatory"
    }
    # 2. Haal de adapter op (gebruik je geinitialiseerde Await uit de module-scope)
    $WiFiAvailableAdapter = Get-WiFiAvailableAdapter | Select-Object -First 1
    if (-not $WiFiAvailableAdapter) { throw "No actieve WiFi-adapter found." }
    # 3. De 'Smart Find' Logica
    # We proberen eerst de cache
    $Network = $WiFiAvailableAdapter.NetworkReport.AvailableNetworks | Where-Object {
      if ($Bssid) { $_.Bssid -eq $Bssid } else { $_.Ssid -eq $Ssid }
    }
    # 4. Nood-scan als het netwerk niet in de cache zit en het GEEN hidden netwerk is
    if (-not $Network -and -not $Hidden) {
      #Write-Host "SSID '$Ssid' niet gevonden in cache, start eenmalige scan..." -ForegroundColor Yellow
      $null = Search-WifiNetworks
      # Probeer het nog één keer na de scan
      $Network = $WiFiAvailableAdapter.NetworkReport.AvailableNetworks | Where-Object { if ($Bssid) { $_.Bssid -eq $Bssid } else { $_.Ssid -eq $Ssid } } | Select-Object -First 1
    }     
    # 5. De genadeslag: als we hem nu nog niet hebben, stoppen we direct
    if (-not $Network) {
      if ($Hidden) {
        throw "Error: Can't find hidden network with BSSID '$Bssid'"
      }
      else {
        throw "Error: SSID '$Ssid' not found even afther scan"
      }
    }
  }
  Process {
    $PasswordValue = $PSBoundParameters['Password']
    $SsidValue = $PSBoundParameters['Ssid']
    # 1. Haal de adapters op
    $AllAdapters = Get-WiFiAvailableAdapter
    # 2. Bepaal op welke adapters we actie ondernemen
    $TargetAdapters = if ($PSBoundParameters.ContainsKey('Index')) {
      foreach ($i in $Index) { $AllAdapters[$i] }
    }
    else {
      $AllAdapters # Standaard: Probeer op ALLE adapters te verbinden
    }
    $Credentials = [Windows.Security.Credentials.PasswordCredential]::new()
    if ($null -ne $passwordValue) {
      if ($AsPlainText) {
        $Credentials.Password = $passwordValue
      }
      else {
        $Credentials.Password = ([System.Net.NetworkCredential]::new([string]::empty, $passwordValue)).Password
      }
    }
    if ([bool]$User) { 
      $Credentials.UserName = $User
    }
    $TargetAdapters | Foreach-Object {  
      # Haal de menselijke naam op voor de huidige adapter in de loop
      $currentId = $_.NetworkAdapter.NetworkAdapterId
      $adapterName = (Get-NetAdapter | Where-Object DeviceID -match $currentId).InterfaceDescription
      Write-Host "Connect over adapter: $adapterName" -ForegroundColor Cyan
      if (-not $Bssid) {
        $Network = $_.NetworkReport.AvailableNetworks | Where-Object { $_.Ssid -match $SsidValue } | Sort-Object NetworkRssiInDecibelMilliwatts -Descending | Select-Object -First 1
      }
      else {
        $Network = $_.NetworkReport.AvailableNetworks | Where-Object { $_.BSsid -match $BSsid }
      }
      $_.Disconnect()
      $Result = Await -WinRtTask ($_.ConnectAsync($Network, $WifiReconnectionKind, $Credentials, $null)) -ResultType ([Windows.Devices.WiFi.WiFiConnectionResult]) -TimeoutMs $TimeoutMS
      # Optioneel: Laat direct weten of het gelukt is voor deze specifieke kaart
      $statusColor = "Yellow"
      if ($Result.ConnectionStatus -eq "Success") { $statusColor = "Green" }
      Write-Host "Status for $adapterName : $($Result.ConnectionStatus)" -ForegroundColor $statusColor
      [PSCustomObject]@{
        ConnectionStatus = $Result.ConnectionStatus  # NIET hernoemen!
        AdapterName      = $adapterName
        Ssid             = $SsidValue
        Index            = [array]::IndexOf($AllAdapters, $_)
        Bssid            = $Network.Bssid
      }
    }
  }
}
function Get-WifiBandName {
  param ([long]$ChannelCenterFrequencyInKilohertz)
  switch ($ChannelCenterFrequencyInKilohertz) {
    { $_ -ge 2400000 -and $_ -le 2500000 } { "2.4 GHz"; break }
    { $_ -ge 5150000 -and $_ -le 5895000 } { "5 GHz"; break }
    { $_ -ge 5925000 -and $_ -le 7125000 } { "6 GHz"; break }
    Default { 
      $ghz = [math]::Floor($_ / 100000) / 10
      "$ghz GHz"
    }
  }
}
# WifiReconnectionKind

$ScriptBlockWifiReconnectionKind = {
  param($commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameters)
  # Gebruik de volledige Enum waarden in een array
  $PossibleValues = @(
    [Windows.Devices.WiFi.WiFiReconnectionKind]::Automatic,
    [Windows.Devices.WiFi.WiFiReconnectionKind]::Manual
  )
  # Filteren op basis van de string-waarde van het Enum object
  $PossibleValues | Where-Object { $_.ToString() -like "$wordToComplete*" } | ForEach-Object {
    # Door een CompletionResult te maken, geef je PowerShell de juiste metadata
    [System.Management.Automation.CompletionResult]::new(
      $_.ToString(),      # De tekst die in de console verschijnt
      $_.ToString(),      # De waarde die daadwerkelijk wordt ingevuld
      'ParameterValue',   # Het type resultaat
      $_.ToString()       # De tooltip (hover tekst)
    )
  }
}

$ScriptBlockSsid = {
  param ($commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameters)
  # 1. Directe Exit bij Hidden
  # Als de gebruiker al -Hidden heeft getypt, kunnen we geen SSID's suggereren
  if ($fakeBoundParameters.ContainsKey('Hidden')) {
    return [System.Management.Automation.CompletionResult]::new(" ", "SSID Unknow (Hidden Mode)", 'Text', "With a hidden network you need to enter the SSID manualy")
  }
  $allAdapters = Get-WiFiAvailableAdapter
  # --- SMART SCAN LOGICA ---
  $networksInCache = ($allAdapters | ForEach-Object { $_.NetworkReport.AvailableNetworks } | Measure-Object).Count
  $cacheIsOld = ($null -eq $script:WiFiLastScanTime -or $script:WiFiLastScanTime -lt (Get-Date).AddMinutes(-5))
  if ($cacheIsOld -or ($networksInCache -le 1)) {
    $null = Search-WifiNetworks
  }
  $targetIndices = if ($fakeBoundParameters.ContainsKey('Index')) { $fakeBoundParameters.Index } else { 0..($allAdapters.Count - 1) }
  $givenBssid = $fakeBoundParameters['Bssid']
  # 2. Haal netwerken op (Filtering op BSSID & overslaan van lege SSIDs)
  $networks = foreach ($i in $targetIndices) {
    if ($null -eq $allAdapters[$i]) { continue }
    $allAdapters[$i].NetworkReport.AvailableNetworks | Sort-Object NetworkRssiInDecibelMilliwatts -Descending | Where-Object { 
      # Sla lege/verborgen netwerken over in de SSID-lijst
      if ([string]::IsNullOrWhiteSpace($_.Ssid)) { return $false }
      if ($givenBssid) {
        $cleanGiven = $givenBssid -replace '[^0-9A-Fa-f]'
        $cleanCurrent = $_.Bssid -replace '[^0-9A-Fa-f]'
        $cleanCurrent -eq $cleanGiven
      }
      else { $true }
    }
  }
  # 3. Bouw de lijst & Sorteer op signaalsterkte
  $networks | Group-Object Ssid | ForEach-Object {
    $strongest = $_.Group | Select-Object -First 1
    $ssidValue = if ($strongest.Ssid -match ' ') { "`"$($strongest.Ssid)`"" } else { $strongest.Ssid }
    $displayText = "$($strongest.Ssid) ($($strongest.SignalBars * 20)% / $($strongest.NetworkRssiInDecibelMilliwatts) dBm)"
    if ($strongest.Ssid -like "$wordToComplete*") {
      [System.Management.Automation.CompletionResult]::new($ssidValue, $displayText, 'ParameterValue', "BSSID: $($strongest.Bssid)")
    }
  } 
}

$ScriptBlockBssid = {
  param($commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameters)
  # 1. Voorbereiding
  $allAdapters = Get-WiFiAvailableAdapter
  $filterSsid = if ($fakeBoundParameters.ContainsKey('Ssid')) { $fakeBoundParameters['Ssid'] -replace '["'']', '' } else { $null }
  $showHidden = $fakeBoundParameters.ContainsKey('Hidden')
  # --- SMART SCAN ---
  $allAvailableNetworks = $allAdapters | ForEach-Object { $_.NetworkReport.AvailableNetworks } | Sort-Object NetworkRssiInDecibelMilliwatts -Descending
  if (($allAvailableNetworks | Measure-Object).Count -eq 0 -or $script:WiFiLastScanTime -lt (Get-Date).AddMinutes(-5)) {
    $allAdapters | ForEach-Object { [Void](Search-WifiNetworks -WiFiAdapter $_) }
    $allAvailableNetworks = $allAdapters | ForEach-Object { $_.NetworkReport.AvailableNetworks }
  }
  $results = New-Object System.Collections.Generic.List[System.Management.Automation.CompletionResult]
  $foundMatch = $false
  # 3. De 'Kook-Logica' (Nauwkeurige Filter)
  foreach ($net in $allAvailableNetworks) {
    $isSsidEmpty = [string]::IsNullOrWhiteSpace($net.Ssid)
    $hasSsidFilter = -not [string]::IsNullOrWhiteSpace($filterSsid)
    # De 'Match' direct bepalen (First-match-wins door 'break')
    $isMatch = switch ($true) {
      ($hasSsidFilter -and $showHidden) { $isSsidEmpty; break } # Scenario 3
      ($hasSsidFilter) { ($net.Ssid -eq $filterSsid); break } # Scenario 2
      ($showHidden) { $isSsidEmpty; break } # Scenario 1b
      default { (-not $isSsidEmpty); break } # Scenario 1a
    }
    if ($isMatch) {
      $foundMatch = $true
      $label = if ($isSsidEmpty) { "$($net.Bssid) [HIDDEN]" } else { "$($net.Bssid) ($($net.Ssid))" }
      $results.Add([System.Management.Automation.CompletionResult]::new(
          $net.Bssid, 
          $label, 
          'ParameterValue', 
          "Signaal: $($net.SignalBars * 20)% ($($net.NetworkRssiInDecibelMilliwatts) dBm) Band ($(Get-WifiBandName $net.ChannelCenterFrequencyInKilohertz))"
        ))
    }
  }
  # 4. De Fallback (Blokkeert de standaard help van de CLI)
  if (-not $foundMatch) {
    # We voegen de spatie toe. Omdat we deze returnen, 'denkt' PS dat er een resultaat is
    # en stopt hij met het tonen van willekeurige commandline history/help.
    $results.Add([System.Management.Automation.CompletionResult]::new(" ", "No match in range", 'Text', "No netwerk found with this filter"))
  }
  return $results | Where-Object { [string]::IsNullOrEmpty($wordToComplete) -or $_.CompletionText -like "$wordToComplete*" }
}

$ScriptBlockIndex = {
  param($commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameters)
  Process {
    $adapters = Get-WiFiAvailableAdapter -TimeoutMs 1000 #this should be instant
    for ($i = 0; $i -lt $adapters.Count; $i++) {
      $id = $adapters[$i].NetworkAdapter.NetworkAdapterId
      $name = (Get-NetAdapter | Where-Object DeviceID -match $id).InterfaceDescription
      [System.Management.Automation.CompletionResult]::new($i, $i, 'ParameterValue', "Adapter $i : $name")
    }
  }
}

$ScriptBlockRadioState = {
  param($commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameters)
  $States = @('On', 'Off') # Meest gebruikte WinRT RadioStates
  $States | Where-Object { $_ -like "$wordToComplete*" } | ForEach-Object {
    [System.Management.Automation.CompletionResult]::new($_, $_, 'ParameterValue', "Set radio to $_")
  }
}

$ScriptBlockDeviceName = {
  param ($commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameters)
  # Haal alle netwerkadapters op
  $adapters = Get-NetAdapter | Where-Object { $_.Name -eq "Wi-Fi" } | Select-Object InterfaceDescription, InstanceID 
  foreach ($adapter in $adapters) {
    # Alleen tonen als de naam matcht met wat er al getypt is
    if ($adapter.InterfaceDescription -like "$wordToComplete*") {
      [System.Management.Automation.CompletionResult]::new(
        "'$($adapter.InterfaceDescription)'",# 1. CompletionText: Dit is wat in de functie belandt (de GUID)
        $adapter.InterfaceDescription, # 2. ListItemText: Dit is wat de gebruiker ziet in het menu
        'ParameterValue',              # 3. ResultType
        "Naam: $($adapter.InterfaceDescription)" # 4. ToolTip: Extra info bij hover
      )
    }
  }
}

Register-ArgumentCompleter -CommandName 'Get-WifiAvailableNetworks' -ParameterName 'DeviceName' -ScriptBlock $ScriptBlockDeviceName
Register-ArgumentCompleter -CommandName 'Set-RadioState' -ParameterName 'SetState' -ScriptBlock $ScriptBlockRadioState
Register-ArgumentCompleter -CommandName 'Disconnect-WifiNetwork' -ParameterName 'Index' -ScriptBlock $ScriptBlockIndex
Register-ArgumentCompleter -CommandName 'Get-WifiCurrentConnection' -ParameterName 'Index' -ScriptBlock $ScriptBlockIndex
Register-ArgumentCompleter -CommandName 'Connect-WiFiNetwork' -ParameterName 'Index' -ScriptBlock $ScriptBlockIndex
Register-ArgumentCompleter -CommandName 'Connect-WiFiNetwork' -ParameterName 'Bssid' -ScriptBlock $ScriptBlockBssid
Register-ArgumentCompleter -CommandName 'Connect-WiFiNetwork' -ParameterName 'Ssid' -ScriptBlock $ScriptBlockSSid
Register-ArgumentCompleter -CommandName 'Connect-wifiNetwork' -ParameterName 'WifiReconnectionKind' -ScriptBlock $ScriptBlockWifiReconnectionKind

Export-ModuleMember Get-RadioState
Export-ModuleMember Set-RadioState
Export-ModuleMember Get-WifiDeviceInformation
Export-Modulemember Get-WiFiAvailableAdapter
Export-ModuleMember Get-WifiAvailableNetworks
Export-ModuleMember Search-WifiNetworks
Export-ModuleMember Get-WifiConnectionProfile
Export-Modulemember Remove-WifiConnectionProfile
Export-ModuleMember Get-WifiCurrentConection
Export-ModuleMember Disconnect-WifiNetwork
Export-ModuleMember Connect-WiFiNetwork