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) { if ($ResultType) { # Voor FindAllAdaptersAsync (IAsyncOperation) $asTask = $asTaskOperation.MakeGenericMethod($ResultType) $netTask = $asTask.Invoke($null, @($WinRtTask)) } else { # Voor ScanAsync (IAsyncAction) $netTask = $asTaskAction.Invoke($null, @($WinRtTask)) } $netTask.Wait(-1) | Out-Null return $netTask.Result } [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() Process { $accessStatus = Await ([Windows.Devices.Radios.Radio]::RequestAccessAsync()) ([Windows.Devices.Radios.RadioAccessStatus]) if ($accessStatus -eq "Allowed") { Await ([Windows.Devices.Radios.Radio]::GetRadiosAsync()) ([System.Collections.Generic.IReadOnlyList[Windows.Devices.Radios.Radio]]) } else { Write-Warning "Geen toegang tot de 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)][ArgumentCompleter({ param($commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameters) $possibleValues = @{Values = @([Windows.Devices.Radios.RadioState]::On, [Windows.Devices.Radios.RadioState]::Off) } if ($fakeBoundParameters.ContainsKey('Type')) { $possibleValues[$fakeBoundParameters.Type] | Where-object { $_ -like "$wordToComplete*" } } else { $possibleValues.Values | Foreach-Object { $_ } } })]$SetState, [parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)][Windows.Devices.Radios.Radio][alias("Radio")][array]$Radios ) process { $Radios.Gettype() Await ($Radios.SetStateAsync($SetState)) ([Windows.Devices.Radios.RadioAccessStatus]) } } [Windows.Devices.WiFi.WiFiAdapter, 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() Process { Await([Windows.Devices.Enumeration.DeviceInformation]::FindAllAsync([Windows.Devices.WiFi.WiFiAdapter]::GetDeviceSelector()))([Windows.Devices.Enumeration.DeviceInformationCollection]) } } Function Get-WiFiAvailableAdapter { [outputtype([Windows.Devices.WiFi.WiFiAdapter])] param() Process { Await([Windows.Devices.WiFi.WiFiAdapter]::FindAllAdaptersAsync())([System.Collections.Generic.IReadOnlyList[Windows.Devices.WiFi.WiFiAdapter]]) } } Function Search-WifiNetworks { [OutputType([Windows.Devices.Wifi.WifiAvailableNetwork])] param( # We laten het type vrij of gebruiken de WinRT klasse [Parameter(ValueFromPipeline = $true)] $WiFiAdapter ) begin { # De 'Smart Load' logica if (-not $WiFiAdapter) { # Gebruik je bestaande stabiele functie als fallback $WiFiAdapter = Get-WiFiAvailableAdapter } } Process { Write-Progress -Activity "WiFi" -Status "Scanning WiFi Networks" [Void](Await($WiFiAdapter.ScanAsync())($null)) Write-Progress -Activity "WiFi" -Completed $script:WiFiLastScanTime = Get-Date return $WifiAdapter.NetworkReport.AvAilableNetworks } } Function Get-WifiConnectionProfile { [outputType([Windows.Networking.Connectivity.NetworkInformation])] param() Process { [Windows.Networking.Connectivity.NetworkInformation]::GetConnectionProfiles() | Where-Object { $_.IsWlanConnectionProfile } } } Function Remove-WifiConnectionProfile { param( [parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)][Windows.Networking.Connectivity.ConnectionProfile] [alias("Profile")] [array]$Profiles ) process { if ($Profiles.CanDelete) { Write-host ("Delete Profile {0}" -f $Profiles.ProfileName) $Profiles.TryDeleteAsync() | Out-Null } else { Write-host ("Can't Delete Profile {0}" -f $Profiles.ProfileName) } } } Function Get-WifiCurrentConection { [outputtype([Windows.Networking.Connectivity.ConnectionProfile])] param() Process { await((Get-WiFiAvailableAdapter).NetworkAdapter.GetConnectedProfileAsync()) ([Windows.Networking.Connectivity.ConnectionProfile]) } } Function Disconnect-WifiNetwork { param( [Parameter(Mandatory = $false)] [int[]]$Index ) Process { $Adapters = Get-WiFiAvailableAdapter if ($null -eq $Adapters -or $Adapters.Count -eq 0) { Write-Warning "No WiFi-adapters found to disconect" return } if ($PSBoundParameters.ContainsKey('Index')) { # Looping through the index given foreach ($i in $Index) { if ($i -ge 0 -and $i -lt $Adapters.Count) { $adapterName = (Get-NetAdapter | Where-Object DeviceID -match $Adapters[$i].NetworkAdapter.NetworkAdapterId).InterfaceDescription Write-host "Disconnected connection on adapter index: $i : $adapterName" -ForegroundColor Cyan $Adapters[$i].Disconnect() } else { Write-Warning "Index $i skipped: does not exist (available: 0 to $($Adapters.Count - 1))." } } } else { # DEFAULT: Alles platgoooien Write-Host "Disconnecting on ALL ($($Adapters.Count)) adapters..." -ForegroundColor Yellow $Adapters.Disconnect() } } } Register-ArgumentCompleter -CommandName 'Disconnect-WifiNetwork' -ParameterName 'Index' -ScriptBlock { param($commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameters) $adapters = Get-WiFiAvailableAdapter 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") } } Function Get-WifiAvailableNetworks { [OutputType([Windows.Devices.Wifi.WifiAvailableNetwork])] param() Process { return (Get-WiFiAvailableAdapter).NetworkReport.AvailableNetworks } } 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 ) 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("Wachtwoord is platte tekst. Gebruik de switch -AsPlainText of voer een SecureString in.") } # 1. Directe validatie van parameters if ($Hidden -and -not $Bssid) { throw "Fout: Bij gebruik van de -Hidden switch is het opgeven van een -Bssid verplicht." } # 2. Haal de adapter op (gebruik je geinitialiseerde Await uit de module-scope) $WiFiAvailableAdapter = Get-WiFiAvailableAdapter | Select-Object -First 1 if (-not $WiFiAvailableAdapter) { throw "Geen actieve WiFi-adapter gevonden." } # 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 sscn" } } } 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 "Verbinden via 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($_.ConnectAsync($Network, [Windows.Devices.WiFi.WiFiReconnectionKind]::Automatic, $Credentials, $null))([Windows.Devices.WiFi.WiFiConnectionResult]) # Optioneel: Laat direct weten of het gelukt is voor deze specifieke kaart $statusColor = "Yellow" if ($Result.ConnectionStatus -eq "Success") { $statusColor = "Green" } Write-Host "Status voor $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" } } } Register-ArgumentCompleter -CommandName 'Connect-WiFiNetwork' -ParameterName 'Ssid' -ScriptBlock { 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 onbekend (Hidden Mode)", 'Text', "Bij een verborgen netwerk moet je de SSID handmatig invullen.") } $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)") } } } Register-ArgumentCompleter -CommandName 'Connect-WiFiNetwork' -ParameterName 'Bssid' -ScriptBlock { 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 } 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(" ", "Geen match in bereik", 'Text', "Geen netwerk gevonden voor deze filters")) } return $results | Where-Object { [string]::IsNullOrEmpty($wordToComplete) -or $_.CompletionText -like "$wordToComplete*" } } Register-ArgumentCompleter -CommandName 'Connect-WiFiNetwork' -ParameterName 'Index' -ScriptBlock { param($commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameters) $adapters = Get-WiFiAvailableAdapter for ($i = 0; $i -lt $adapters.Count; $i++) { $name = $adapters[$i].NetworkAdapter.NetworkItem.NetworkId # Of gebruik Get-NetAdapter voor de 'echte' naam [System.Management.Automation.CompletionResult]::new($i, $i, 'ParameterValue', "Adapter $i : $name") } } 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 |