WiFiProfileManagement.psm1

$script:localizedData = Import-LocalizedData -BaseDirectory "$PSScriptRoot\en-US" -FileName WiFiProfileManagement.strings.psd1

<#
    .SYNOPSIS
        Attempts to connect to a specific network.
 
    .PARAMETER ProfileName
        The name of the profile to be connected. Profile names are case-sensitive.
 
    .PARAMETER ConnectionMode
        Specifies the mode of the connection. Valid values are Profile,TemporaryProfile,DiscoveryProfile,DiscoveryUnsecure, and Auto.
 
    .PARAMETER Dot11BssType
        A value that indicates the BSS type of the network. If a profile is provided, this BSS type must be the same as the one in the profile.
 
    .PARAMETER WiFiAdapterName
        Specifies the name of the wireless network adapter on the machine. This is used to obtain the Guid of the interface.
 
    .EXAMPLE
        PS C:\>Connect-WiFiProfile -ProfileName FreeWiFi
 
        This example connects to the FreeWiFi profile which is already saved on the local machine.
         
    .EXAMPLE
        PS C:\> $password = Read-Host -AsSecureString
        ************
 
        PS C:\> New-WiFiProfile -ProfileName MyNetwork -ConnectionMode auto -Authentication WPA2PSK -Encryption AES -Password $password
 
        The operation was successful.
        PS C:\> Connect-WiFiProfile -ProfileName MyNetwork
 
        This example demonstrates how to create a WiFi profile and then connect to it.
 
    .NOTES
        https://msdn.microsoft.com/en-us/library/windows/desktop/ms706613(v=vs.85).aspx
#>

function Connect-WiFiProfile
{
    [OutputType([void])]
    [CmdletBinding()]
    param
    (
        [Parameter(Mandatory = $true)]
        [System.String]
        $ProfileName,

        [Parameter()]
        [ValidateSet('Profile', 'TemporaryProfile', 'DiscoverySecure', 'DiscoveryUnsecure', 'Auto')]
        [System.String]
        $ConnectionMode = 'Profile',

        [Parameter()]
        [ValidateSet('Any', 'Independent', 'Infrastructure')]
        [System.String]
        $Dot11BssType = 'Any',

        [Parameter()]
        [System.String]
        $WiFiAdapterName
    )

    begin
    {
        $interfaceInfo = Get-InterfaceInfo -WiFiAdapterName $WiFiAdapterName

        if ($interfaceInfo.Count -gt 1)
        {
            throw $Script:localizedData.ErrorMoreThanOneInterface
        }
    }
    process
    {
        try
        {
            $clientHandle = New-WiFiHandle
            $connectionParameterList = New-WiFiConnectionParameter -ProfileName $ProfileName -ConnectionMode $ConnectionMode -Dot11BssType $Dot11BssType
            Invoke-WlanConnect -ClientHandle $clientHandle -InterfaceGuid $interfaceInfo.InterfaceGuid -ConnectionParameterList $connectionParameterList
        }
        catch
        {
            Write-Error $PSItem
        }
        finally
        {
            if ($clientHandle)
            {
                Remove-WiFiHandle -ClientHandle $clientHandle
            }
        }
    }
}

<#
    .SYNOPSIS
        Retrieves the list of available networks on a wireless LAN interface.

    .PARAMETER WiFiAdapterName
        Specifies the name of the wireless network adapter on the machine. This is used to obtain the Guid of the interface.
        The default value is 'Wi-Fi'

    .PARAMETER InvokeScan
        Switch so a scan is ran to discover additional wireless networks.

    .EXAMPLE
        PS C:\>Get-WiFiAvailableNetwork

        SSID SignalStength SecurityEnabled dot11DefaultAuthAlgorithm dot11DefaultCipherAlgorithm
        ---- ------------- --------------- ------------------------- ---------------------------
                                63 True DOT11_AUTH_ALGO_RSNA_PSK DOT11_CIPHER_ALGO_CCMP
        gogoinflight 63 False DOT11_AUTH_ALGO_80211_OPEN DOT11_CIPHER_ALGO_NONE
        
    .NOTES
        https://msdn.microsoft.com/en-us/library/windows/desktop/ms706749(v=vs.85).aspx
#>

function Get-WiFiAvailableNetwork
{
    [CmdletBinding()]
    [OutputType([WiFi.ProfileManagement+WLAN_AVAILABLE_NETWORK])]
    param
    (
        [Parameter()]
        [System.String]
        $WiFiAdapterName,

        [Parameter()]
        [switch]
        $InvokeScan
    )

    try
    {
        if ($InvokeScan.IsPresent)
        {
            Search-WiFiNetwork -WiFiAdapterName $WiFiAdapterName
            # docs says Windows certified wifi drivers should complete a scan in 4 seconds
            # probably should figure out how to do this the right way
            Start-Sleep -Seconds 4
        }

        $interfaceInfo = Get-InterfaceInfo -WiFiAdapterName $WiFiAdapterName

        $flag = 0
        $networkList = @()
        $pointerCollection = @()
        $clientHandle = New-WiFiHandle

        foreach ($interface in $interfaceInfo)
        {
            $networkPointer = 0
            $result = [WiFi.ProfileManagement]::WlanGetAvailableNetworkList(
                $clientHandle,
                $interface.InterfaceGuid,
                $flag,
                [IntPtr]::zero,
                [ref] $networkPointer
            )

            if ($result -ne 0)
            {
                $errorMessage = Format-Win32Exception -ReturnCode $result
                throw $($script:localizedData.ErrorGetAvailableNetworkList -f $errorMessage)
            }

            $availableNetworks = [WiFi.ProfileManagement+WLAN_AVAILABLE_NETWORK_LIST]::new($networkPointer)
            $pointerCollection += $networkPointer

            foreach ($network in $availableNetworks.wlanAvailableNetwork)
            {
                $networkResult = [WiFi.ProfileManagement+WLAN_AVAILABLE_NETWORK] $network
                $networkList += Add-DefaultProperty -InputObject $networkResult -InterfaceInfo $interface
            }
        }

        $networkList
    }
    catch
    {
        $PSItem
    }
    finally
    {
        Invoke-WlanFreeMemory -Pointer $pointerCollection

        if ($clientHandle)
        {
            Remove-WiFiHandle -ClientHandle $clientHandle
        }
    }
}

<#
    .SYNOPSIS
        Returns the wifi connection attributes.
 
    .DESCRIPTION
        Returns the wifi connection attributes by calling the WlanQueryInterface function
        with the wlan_intf_opcode_current_connection opcode.
 
    .PARAMETER WiFiAdapterName
        Specifies the name of the wifi adapter to retrieve the connection attributes from.
 
    .EXAMPLE
        Get-WiFiConnectionAttributes
 
        WiFiAdapter : Wi-Fi
        InterfaceGuid : {28A46E1B-6284-41CA-9CB6-AA4C18A9254B}
        isState : connected
        wlanConnectionMode : wlan_connection_mode_profile
        strProfileName : 145AW Commercial Wireless
        wlanAssociationAttributes : WiFi.ProfileManagement+WLAN_ASSOCIATION_ATTRIBUTES
        wlanSecurityAttributes : WiFi.ProfileManagement+WLAN_SECURITY_ATTRIBUTES
 
    .EXAMPLE
        $attributes = Get-WiFiConnectionAttributes
 
        PS > $attributes.wlanAssociationAttributes
 
        dot11Ssid : Commercial Wireless
        dot11BssType : Infrastructure
        dot11Bssid : 3C:3A:C3:AE:D3:3E
        dot11PhyType : dot11_phy_type_vht
        uDot11PhyIndex : 4
        wlanSignalQuality : 82
        ulRxRate : 173300
        ulTxRate : 173300
 
        This example illustrates how to view the wlanAssociatedAttributes.
 
#>

function Get-WiFiConnectionAttributes
{
    [CmdletBinding()]
    param
    (
        [Parameter()]
        [System.String]
        $WiFiAdapterName
    )

    try
    {
        $interfaceInfo = Get-InterfaceInfo -WiFiAdapterName $WiFiAdapterName

        $result = @()
        $outDataCollection = @()
        $clientHandle = New-WiFiHandle

        foreach ($interface in $interfaceInfo)
        {
            $outData = [System.IntPtr]::zero
            $dataSize = [System.Runtime.InteropServices.Marshal]::SizeOf($outData)

            $resultCode = [WiFi.ProfileManagement]::WlanQueryInterface(
                $clientHandle,
                [ref] $interface.InterfaceGuid,
                [WiFi.ProfileManagement+WLAN_INTF_OPCODE]::wlan_intf_opcode_current_connection,
                [IntPtr]::zero,
                [ref]$dataSize,
                [ref]$outData,
                [IntPtr]::zero
            )

            if ($resultCode -ne 0)
            {
                Write-Error -Message ($script:localizedData.ErrorFailedWithExitCode -f $resultCode)
            }

            $attributes = [System.Runtime.InteropServices.Marshal]::ptrToStructure(
                $outData,
                [System.Type]([WiFi.ProfileManagement+WLAN_CONNECTION_ATTRIBUTES])
            )

            $outDataCollection += $outData

            $result += Add-DefaultProperty -InputObject $attributes -InterfaceInfo $interface
        }

        $result
    }
    catch
    {
        $PSItem
    }
    finally
    {
        if ($clientHandle)
        {
            Remove-WiFiHandle -ClientHandle $clientHandle
        }

        if ($outDataCollection)
        {
            Invoke-WlanFreeMemory -Pointer $outDataCollection
        }
    }
}

<#
    .SYNOPSIS
        Retrieves a list of the basic service set (BSS) entries of the wireless network or networks on a given wireless LAN interface.
 
    .PARAMETER WiFiAdapterName
        Specifies the name of the wireless network adapter on the machine. This is used to obtain the Guid of the interface.
        The default value is 'Wi-Fi'
 
    .PARAMETER InvokeScan
        Switch so a scan is ran to discover additional wireless networks.
 
    .EXAMPLE
        Get-WifiNetworkBssList
 
        SSID PhyId APMacAddress Dot11BssType RSSI LinkQuality InRegulatoryDomain BeaconPeriod TimeStamp HostTimeStamp
        ---- ----- ------------ ------------ ---- ----------- ------------------ ------------ --------- -------------
        SpectrumSetup-4A 0 0C-73-29-E0-7A-4B Infrastructure -91 14 True 0 3244164813477 133399628005404767
        Spectrum Mobile 0 86-97-33-11-60-6D Infrastructure -91 14 True 0 2068856218275 133399627979117712
        SpectrumSetup-4A 0 46-EB-42-E5-46-70 Infrastructure -81 35 True 0 929857743696 133399627970981871
                             0 56-EB-42-E5-46-70 Infrastructure -81 35 True 0 929857743335 133399627970981871
                             0 36-EB-42-E5-46-70 Infrastructure -82 33 True 0 929857742974 133399627970981871
                             0 22-EF-BD-52-06-C5 Infrastructure -72 62 True 0 1395138969665 133399627972633616
        VR217-F122 0 7C-8B-CA-3B-F1-22 Infrastructure -87 22 True 0 3950632387792 133399627972078465
                             0 8E-73-29-E0-7A-4C Infrastructure -78 43 True 0 3244161846928 133399627976111064
        SpectrumSetup-4A 0 0C-73-29-E0-7A-4C Infrastructure -81 35 True 0 3244161843584 133399627976111064
                             0 7E-73-29-E0-7A-4C Infrastructure -76 50 True 0 3244161844093 133399627976111064
#>

function Get-WifiNetworkBssList
{
    [CmdletBinding()]
    param
    (
        [Parameter()]
        [System.String]
        $WiFiAdapterName,

        [Parameter()]
        [switch]
        $InvokeScan
    )

    try
    {
        if ($InvokeScan.IsPresent)
        {
            Search-WiFiNetwork -WiFiAdapterName $WiFiAdapterName
            # docs says Windows certified wifi drivers should complete a scan in 4 seconds
            # probably should figure out how to do this the right way
            Start-Sleep -Seconds 4
        }

        $networkList = @()
        $interfaceInfo = Get-InterfaceInfo -WiFiAdapterName $WiFiAdapterName
        $clientHandle = New-WiFiHandle
        $ssid = [System.IntPtr]::zero
        $dot11BssType = [WiFi.ProfileManagement+DOT11_BSS_TYPE]::Any

        foreach ($interface in $interfaceInfo)
        {
            $bssListPointer = [System.IntPtr]::zero
            $result = [WiFi.ProfileManagement]::WlanGetNetworkBssList(
                $clientHandle,
                $interface.InterfaceGuid,
                $ssid,
                $dot11BssType,
                $false,
                [System.IntPtr]::zero,
                [ref] $bssListPointer
            )

            if ($result -ne 0)
            {
                $errorMessage = Format-Win32Exception -ReturnCode $result
                throw $($script:localizedData.ErrorGetNetworkBssList -f $errorMessage)
            }

            $bssEntries = [WiFi.ProfileManagement+WLAN_BSS_LIST]::new($bssListPointer)
            $pointerCollection += $bssListPointer

            foreach ($bssEntry in $bssEntries.wlanBssEntries)
            {
                $bssResult = Format-BssEntry -BssEntry $bssEntry
                $networkList += Add-DefaultProperty -InputObject $bssResult -InterfaceInfo $interface
            }
        }

        $networkList
    }
    catch
    {
        $PSItem
    }
    finally
    {
        Invoke-WlanFreeMemory -Pointer $pointerCollection

        if ($clientHandle)
        {
            Remove-WiFiHandle -ClientHandle $clientHandle
        }
    }
}

<#
    .SYNOPSIS
        Lists the wireless profiles and their configuration settings.

    .PARAMETER ProfileName
        The name of the WiFi profile.

    .PARAMETER WiFiAdapterName
        Specifies the name of the wireless network adapter on the machine. This is used to obtain the Guid of the interface.

    .PARAMETER ClearKey
        Specifies if the password of the profile is to be returned.

    .EXAMPLE
        PS C:\>Get-WiFiProfile -ProfileName TestWiFi

        SSIDName : TestWiFi
        ConnectionMode : auto
        Authentication : WPA2PSK
        Encryption : AES
        Password :

        Get the WiFi profile information on wireless profile TestWiFi

    .EXAMPLE
        PS C:\>Get-WiFiProfile -ProfileName TestWiFi -CLearKey

        SSIDName : TestWiFi
        ConnectionMode : auto
        Authentication : WPA2PSK
        Encryption : AES
        Password : password1

        This examples shows the use of the ClearKey switch to return the WiFi profile password.

    .EXAMPLE
        PS C:\>Get-WiFiProfile | where {$PSItem.ConnectionMode -eq 'auto' -and $PSItem.Authentication -eq 'open'}

        This example shows how to find WiFi profiles with insecure connection settings.
#>

function Get-WiFiProfile
{
    [CmdletBinding()]
    [OutputType([System.Management.Automation.PSCustomObject])]
    param
    (
        [Parameter(Position = 0)]
        [System.String[]]
        $ProfileName,

        [Parameter()]
        [System.String]
        $WiFiAdapterName,

        [Parameter()]
        [Switch]
        $ClearKey
    )

    try
    {
        $result = @()
        $profileListPointer = 0
        $interfaceInfo = Get-InterfaceInfo -WiFiAdapterName $WiFiAdapterName
        $clientHandle = New-WiFiHandle

        if ($ClearKey)
        {
            $wlanProfileFlags = 13
        }
        else
        {
            $wlanProfileFlags = 0
        }

        if (!$ProfileName)
        {
            foreach ($interface in $interfaceInfo)
            {
                [void] [WiFi.ProfileManagement]::WlanGetProfileList(
                    $clientHandle,
                    $interface.InterfaceGuid,
                    [IntPtr]::zero,
                    [ref] $profileListPointer
                )

                $wiFiProfileList = [WiFi.ProfileManagement+WLAN_PROFILE_INFO_LIST]::new($profileListPointer)
                $ProfileName = ($wiFiProfileList.ProfileInfo).strProfileName
            }
        }

        foreach ($wiFiProfile in $ProfileName)
        {
            foreach ($interface in $interfaceInfo)
            {
                $profileInfo = Get-WiFiProfileInfo -ProfileName $wiFiProfile -InterfaceGuid $interface.InterfaceGuid -ClientHandle $clientHandle -WlanProfileFlags $wlanProfileFlags
                $result += Add-DefaultProperty -InputObject $profileInfo -InterfaceInfo $interface
            }
        }

        $result
    }
    catch
    {
        Write-Error $PSItem
    }
    finally
    {
        if ($clientHandle)
        {
            Remove-WiFiHandle -ClientHandle $clientHandle
        }
    }
}

<#
    .SYNOPSIS
        Retrieves the RSSI (Received signal strength indicator.
 
    .PARAMETER WiFiAdapterName
        Specifies the name of the wifi adapter to get the RSSI from.
 
    .EXAMPLE
        Get-WiFiRssi
 
        Rssi WiFiAdapterName InterfaceGuid
        ---- --------------- -------------
        -36 Wi-Fi {ad33cbbf-771c-4864-bba9-705592093534}
 
        This examples retrieves the RSSI from the default wifi network adaptor.
#>

function Get-WiFiRssi
{
    [CmdletBinding()]
    param
    (
        [Parameter()]
        [System.String]
        $WiFiAdapterName
    )

    try
    {
        $result = @()
        $pointerCollection = @()
        $interfaceInfo = Get-InterfaceInfo -WiFiAdapterName $WiFiAdapterName
        $clientHandle = New-WiFiHandle

        $outData = [System.IntPtr]::zero
        [int]$dataSize = 0

        foreach ($interface in $interfaceInfo)
        {
            $resultCode = [WiFi.ProfileManagement]::WlanQueryInterface(
                $clientHandle,
                [ref] $interface.InterfaceGuid,
                [WiFi.ProfileManagement+WLAN_INTF_OPCODE]::wlan_intf_opcode_rssi,
                [IntPtr]::zero,
                [ref]$dataSize,
                [ref]$outData,
                [IntPtr]::zero
            )

            if ($resultCode -ne 0)
            {
                return $resultCode
            }

            $pointerCollection += $outData
            $rssi = [PSCustomObject]@{
                Rssi = [System.Runtime.InteropServices.Marshal]::ReadInt32($outData)
            }

            $result += Add-DefaultProperty -InputObject $rssi -InterfaceInfo $interface
        }

        $result
    }
    catch
    {
        $PSItem
    }
    finally
    {
        if ($clientHandle)
        {
            Remove-WiFiHandle -ClientHandle $clientHandle
        }

        if ($outData)
        {
            Invoke-WlanFreeMemory -Pointer $pointerCollection
        }
    }
}

<#
    .SYNOPSIS
        Creates the content of a specified wireless profile.
        
    .DESCRIPTION
        Creates the content of a wireless profile by calling the WlanSetProfile native function but with the override parameter set to false.

    .PARAMETER ProfileName
        The name of the wireless profile to be created. Profile names are case sensitive.

    .PARAMETER ConnectionMode
        Indicates whether connection to the wireless LAN should be automatic ("auto") or initiated ("manual") by user.

    .PARAMETER Authentication
        Specifies the authentication method to be used to connect to the wireless LAN.

    .PARAMETER Encryption
        Sets the data encryption to use to connect to the wireless LAN.

    .PARAMETER Password
        The network key or passphrase of the wireless profile in the form of a secure string.

    .PARAMETER ConnectHiddenSSID
        Specifies whether the profile can connect to networks which does not broadcast SSID. The default is false.

    .PARAMETER EAPType
        (Only 802.1X) Specifies the type of 802.1X EAP. You can select "PEAP"(aka MSCHAPv2) or "TLS".

    .PARAMETER ServerNames
        (Only 802.1X) Specifies the server that will be connect to validate certification.

    .PARAMETER TrustedRootCA
        (Only 802.1X) Specifies the certificate thumbprint of the Trusted Root CA.

    .PARAMETER XmlProfile
        The XML representation of the profile.

    .EXAMPLE
        PS C:\>$password = Read-Host -AsSecureString
        **********

        PS C:\>New-WiFiProfile -ProfileName MyNetwork -ConnectionMode auto -Authentication WPA2PSK -Encryption AES -Password $password

        This examples shows how to create a wireless profile by using the individual parameters.

    .EXAMPLE
        PS C:\>New-WiFiProfile -ProfileName OneXNetwork -Authentication WPA2 -Encryption AES -EAPType PEAP -TrustedRootCA '041101cca5b336a9c6e50d173489f5929e1b4b00'

        This examples shows how to create a 802.1X wireless profile by using the individual parameters.

    .EXAMPLE
        PS C:\>$templateProfileXML = @"
        <?xml version="1.0"?>
        <WLANProfile xmlns="http://www.microsoft.com/networking/WLAN/profile/v1">
            <name>MyNetwork</name>
            <SSIDConfig>
                <SSID>
                    <name>MyNetwork</name>
                </SSID>
            </SSIDConfig>
            <connectionType>ESS</connectionType>
            <connectionMode>manual</connectionMode>
            <MSM>
                <security>
                    <authEncryption>
                        <authentication>WPA2PSK</authentication>
                        <encryption>AES</encryption>
                        <useOneX>false</useOneX>
                    </authEncryption>
                    <sharedKey>
                        <keyType>passPhrase</keyType>
                        <protected>false</protected>
                        <keyMaterial>password1</keyMaterial>
                    </sharedKey>
                </security>
            </MSM>
        </WLANProfile>
        "@

        PS C:\>New-WiFiProfile -XmlProfile $templateProfileXML

        This example demonstrates how to update a wireless profile with the XmlProfile parameter.

    .NOTES
        https://msdn.microsoft.com/en-us/library/windows/desktop/ms706795(v=vs.85).aspx
        https://msdn.microsoft.com/en-us/library/windows/desktop/ms707381(v=vs.85).aspx
#>

function New-WiFiProfile
{
    [CmdletBinding(DefaultParameterSetName = 'UsingArguments')]
    param
    (
        [Parameter(Mandatory = $true, Position = 0, ParameterSetName = 'UsingArguments')]
        [Parameter(Mandatory = $true, Position = 0, ParameterSetName = 'UsingArgumentsWithEAP')]
        [Alias('SSID', 'Name')]
        [System.String]
        $ProfileName,

        [Parameter(ParameterSetName = 'UsingArguments')]
        [Parameter(ParameterSetName = 'UsingArgumentsWithEAP')]
        [ValidateSet('manual', 'auto')]
        [System.String]
        $ConnectionMode = 'auto',

        [Parameter(ParameterSetName = 'UsingArguments')]
        [Parameter(ParameterSetName = 'UsingArgumentsWithEAP')]
        [ValidateSet('open', 'shared', 'WPA', 'WPAPSK', 'WPA2', 'WPA2PSK', 'WPA3SAE', 'WPA3ENT192', 'OWE')]
        [System.String]
        $Authentication = 'WPA2PSK',

        [Parameter(ParameterSetName = 'UsingArguments')]
        [Parameter(ParameterSetName = 'UsingArgumentsWithEAP')]
        [ValidateSet('none', 'WEP', 'TKIP', 'AES', 'GCMP256')]
        [System.String]
        $Encryption = 'AES',

        [Parameter(ParameterSetName = 'UsingArguments')]
        [System.Security.SecureString]
        $Password,

        [Parameter(ParameterSetName = 'UsingArguments')]
        [Parameter(ParameterSetName = 'UsingArgumentsWithEAP')]
        [System.Boolean]
        $ConnectHiddenSSID = $false,

        [Parameter(ParameterSetName = 'UsingArgumentsWithEAP')]
        [ValidateSet('PEAP', 'TLS')]
        [System.String]
        $EAPType,

        [Parameter(ParameterSetName = 'UsingArgumentsWithEAP')]
        [AllowEmptyString()]
        [System.String]
        $ServerNames = '',

        [Parameter(ParameterSetName = 'UsingArgumentsWithEAP')]
        [System.String]
        $TrustedRootCA,

        [Parameter()]
        [System.String]
        $WiFiAdapterName,

        [Parameter(Mandatory = $true, ParameterSetName = 'UsingXml')]
        [System.String]
        $XmlProfile,

        [Parameter(DontShow = $true)]
        [System.Boolean]
        $Overwrite = $false
    )

    try
    {
        $interfaceInfo = Get-InterfaceInfo -WiFiAdapterName $WiFiAdapterName

        if ($interfaceInfo.Count -gt 1)
        {
            throw $Script:localizedData.ErrorNeedSingleAdapterName
        }

        $clientHandle = New-WiFiHandle
        $flags = 0
        $reasonCode = [IntPtr]::Zero

        if ($XmlProfile)
        {
            $profileXML = $XmlProfile
        }
        else
        {
            $newProfileParameters = @{
                ProfileName       = $ProfileName
                ConnectionMode    = $ConnectionMode
                Authentication    = $Authentication
                Encryption        = $Encryption
                Password          = $Password
                ConnectHiddenSSID = $ConnectHiddenSSID
                EAPType           = $EAPType
                ServerNames       = $ServerNames
                TrustedRootCA     = $TrustedRootCA
            }

            $profileXML = New-WiFiProfileXml @newProfileParameters
        }

        $profilePointer = [System.Runtime.InteropServices.Marshal]::StringToHGlobalUni($profileXML)

        $returnCode = [WiFi.ProfileManagement]::WlanSetProfile(
            $clientHandle,
            [ref] $interfaceInfo.InterfaceGuid,
            $flags,
            $profilePointer,
            [IntPtr]::Zero,
            $Overwrite,
            [IntPtr]::Zero,
            [ref]$reasonCode
        )

        $returnCodeMessage = Format-Win32Exception -ReturnCode $returnCode
        $reasonCodeMessage = Format-WiFiReasonCode -ReasonCode $reasonCode

        if ($returnCode -eq 0)
        {
            Write-Verbose -Message $returnCodeMessage
        }
        else
        {
            throw $returnCodeMessage
        }

        Write-Verbose -Message $reasonCodeMessage
    }
    catch
    {
        $PSItem
    }
    finally
    {
        if ($clientHandle)
        {
            Remove-WiFiHandle -ClientHandle $clientHandle
        }
    }
}

<#
    .SYNOPSIS
        Deletes a WiFi profile.

    .PARAMETER ProfileName
        The name of the profile to be deleted. Profile names are case-sensitive.

    .PARAMETER WiFiAdapterName
        Specifies the name of the wireless network adapter on the machine. This is used to obtain the Guid of the interface.
        The default value is 'Wi-Fi'
        
    .EXAMPLE
    PS C:\>Remove-WiFiProfile -ProfileName FreeWiFi

    This examples deletes the FreeWiFi profile.
#>

function Remove-WiFiProfile
{
    [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'High')]
    Param
    (
        [Parameter(Position = 0, Mandatory = $true, ValueFromPipelineByPropertyName = $true)]
        [System.String[]]
        $ProfileName,

        [Parameter(Position = 1)]
        [System.String]
        $WiFiAdapterName
    )

    begin
    {
        $interfaceInfo = Get-InterfaceInfo -WiFiAdapterName $WiFiAdapterName
    }
    process
    {
        try
        {
            $clientHandle = New-WiFiHandle

            foreach ($wiFiProfile in $ProfileName)
            {
                foreach ($interface in $interfaceInfo)
                {
                    if ($PSCmdlet.ShouldProcess("$($script:localizedData.ShouldProcessDelete -f $WiFiProfile)"))
                    {
                        $deleteProfileResult = [WiFi.ProfileManagement]::WlanDeleteProfile(
                            $clientHandle,
                            $interface.InterfaceGuid,
                            $wiFiProfile,
                            [IntPtr]::Zero
                        )

                        $deleteProfileResultMessage = Format-Win32Exception -ReturnCode $deleteProfileResult

                        if ($deleteProfileResult -ne 0)
                        {
                            Write-Error -Message ($script:localizedData.ErrorDeletingProfile -f $deleteProfileResultMessage)
                        }
                        else
                        {
                            Write-Verbose -Message $deleteProfileResultMessage
                        }
                    }
                }
            }
        }
        catch
        {
            Write-Error $PSItem
        }
        finally
        {
            if ($clientHandle)
            {
                Remove-WiFiHandle -ClientHandle $clientHandle
            }
        }
    }
}

<#
    .SYNOPSIS
        Requests a scan for available wifi networks on the indicated interface.
        The scan is requested by calling the WlanScan function in the WlanApi
 
    .PARAMETER WiFiAdapterName
        Specifies the name of the wireless network adapter on the machine. This is used to obtain the Guid of the interface.
 
    .EXAMPLE
        Search-WiFiNetwork -WiFiAdapterName WiFi
 
        This examples will search for WiFi networks on the WiFi adapter.
#>

function Search-WiFiNetwork
{
    [CmdletBinding()]
    param
    (
        [Parameter()]
        [System.String]
        $WiFiAdapterName
    )

    try
    {
        $interfaceInfo = Get-InterfaceInfo -WiFiAdapterName $WiFiAdapterName

        $clientHandle = New-WiFiHandle

        foreach ($interface in $interfaceInfo)
        {
            $resultCode = [WiFi.ProfileManagement]::WlanScan(
                $clientHandle,
                [ref] $interface.InterfaceGuid,
                [IntPtr]::zero,
                [IntPtr]::zero,
                [IntPtr]::zero
            )

            if ($resultCode -ne 0)
            {
                $resultCode
            }
        }
    }
    catch
    {
        $PSItem
    }
    finally
    {
        if ($clientHandle)
        {
            Remove-WiFiHandle -ClientHandle $clientHandle
        }
    }
}

<#
    .SYNOPSIS
        Toggles the software wifi radio state on/off.

    .PARAMETER WiFiAdapterName
        Specifies the name of the wireless network adapter on the machine. This is used to obtain the Guid of the interface.
        The default value is 'Wi-Fi'

    .PARAMETER State
        Specifies the state of the wifi radio. Valid values are on/off.

    .EXAMPLE
        Set-WiFiInterface -State On

        In this example the wifi radio is being turned on.

    .EXAMPLE
        Set-WiFiInterface -State Off

        In this example the wifi radio is being turned off.
#>

function Set-WiFiInterface
{
    [CmdletBinding()]
    param
    (
        [Parameter()]
        [System.String]
        $WiFiAdapterName,

        [Parameter(Mandatory = $true)]
        [ValidateSet('On','Off')]
        [string]
        $State
    )

    try
    {
        $interfaceInfo = Get-InterfaceInfo -WiFiAdapterName $WiFiAdapterName

        $clientHandle = New-WiFiHandle

        $radioStatePtr = [System.IntPtr]::new(0L)
        $radioState = [WiFi.ProfileManagement+WlanPhyRadioState]::new()
        $radioState.dot11SoftwareRadioState = [WiFi.ProfileManagement+Dot11RadioState]::$State
        $radioState.dot11HardwareRadioState = [WiFi.ProfileManagement+Dot11RadioState]::$State
        $opCode = [WiFi.ProfileManagement+WLAN_INTF_OPCODE]::wlan_intf_opcode_radio_state
        $radioStateSize = [System.Runtime.InteropServices.Marshal]::SizeOf($radioState)
        $radioStatePtr = [System.Runtime.InteropServices.Marshal]::AllocHGlobal($radioStateSize)

        [System.Runtime.InteropServices.Marshal]::StructureToPtr($radioState, $radioStatePtr, $false)

        foreach ($interface in $interfaceInfo)
        {
            $resultCode = [WiFi.ProfileManagement]::WlanSetInterface(
                $clientHandle,
                [ref] $interface.InterfaceGuid,
                $opCode,
                [System.Runtime.InteropServices.Marshal]::SizeOf([System.Type]([WiFi.ProfileManagement+WlanPhyRadioState])),
                $radioStatePtr,
                [IntPtr]::zero
            )

            if ($resultCode -ne 0)
            {
                $resultCode
            }
        }
    }
    catch
    {
        Write-Error -Exception $PSItem
    }
    finally
    {
        if ($clientHandle)
        {
            [System.Runtime.InteropServices.Marshal]::FreeHGlobal($radioStatePtr)
            Remove-WiFiHandle -ClientHandle $clientHandle
        }
    }
}

<#
    .SYNOPSIS
        Sets the content of a specified wireless profile.
        
    .DESCRIPTION
        Calls the WlanSetProfile native function with override parameter set to true.

    .PARAMETER ProfileName
        The name of the wireless profile to be updated. Profile names are case sensitive.

    .PARAMETER ConnectionMode
        Indicates whether connection to the wireless LAN should be automatic ("auto") or initiated ("manual") by user.

    .PARAMETER Authentication
        Specifies the authentication method to be used to connect to the wireless LAN.

    .PARAMETER Encryption
        Sets the data encryption to use to connect to the wireless LAN.

    .PARAMETER Password
        The network key or passphrase of the wireless profile in the form of a secure string.

    .PARAMETER ConnectHiddenSSID
        Specifies whether the profile can connect to networks which does not broadcast SSID. The default is false.

    .PARAMETER EAPType
        (Only 802.1X) Specifies the type of 802.1X EAP. You can select "PEAP"(aka MSCHAPv2) or "TLS".

    .PARAMETER ServerNames
        (Only 802.1X) Specifies the server that will be connect to validate certification.

    .PARAMETER TrustedRootCA
        (Only 802.1X) Specifies the certificate thumbprint of the Trusted Root CA.

    .PARAMETER XmlProfile
        The XML representation of the profile.

    .EXAMPLE
        PS C:\>$password = Read-Host -AsSecureString
        **********

        PS C:\>Set-WiFiProfile -ProfileName MyNetwork -ConnectionMode auto -Authentication WPA2PSK -Encryption AES -Password $password

        This examples shows how to update or create a wireless profile by using the individual parameters.

    .EXAMPLE
        PS C:\>$templateProfileXML = @"
        <?xml version="1.0"?>
        <WLANProfile xmlns="http://www.microsoft.com/networking/WLAN/profile/v1">
            <name>MyNetwork</name>
            <SSIDConfig>
                <SSID>
                    <name>MyNetwork</name>
                </SSID>
            </SSIDConfig>
            <connectionType>ESS</connectionType>
            <connectionMode>manual</connectionMode>
            <MSM>
                <security>
                    <authEncryption>
                        <authentication>WPA2PSK</authentication>
                        <encryption>AES</encryption>
                        <useOneX>false</useOneX>
                    </authEncryption>
                    <sharedKey>
                        <keyType>passPhrase</keyType>
                        <protected>false</protected>
                        <keyMaterial>password1</keyMaterial>
                    </sharedKey>
                </security>
            </MSM>
        </WLANProfile>
        "@

        PS C:\>Set-WiFiProfile -XmlProfile $templateProfileXML

        This example demonstrates how to update a wireless profile with the XmlProfile parameter.

    .NOTES
        https://msdn.microsoft.com/en-us/library/windows/desktop/ms706795(v=vs.85).aspx
        https://msdn.microsoft.com/en-us/library/windows/desktop/ms707381(v=vs.85).aspx
#>

function Set-WiFiProfile
{
    [CmdletBinding(DefaultParameterSetName = 'UsingArguments')]
    param
    (
        [Parameter(Mandatory = $true, Position = 0, ParameterSetName = 'UsingArguments')]
        [Parameter(Mandatory = $true, Position = 0, ParameterSetName = 'UsingArgumentsWithEAP')]
        [Alias('SSID', 'Name')]
        [System.String]
        $ProfileName,

        [Parameter(ParameterSetName = 'UsingArguments')]
        [Parameter(ParameterSetName = 'UsingArgumentsWithEAP')]
        [ValidateSet('manual', 'auto')]
        [System.String]
        $ConnectionMode = 'auto',

        [Parameter(ParameterSetName = 'UsingArguments')]
        [Parameter(ParameterSetName = 'UsingArgumentsWithEAP')]
        [ValidateSet('open', 'shared', 'WPA', 'WPAPSK', 'WPA2', 'WPA2PSK', 'WPA3SAE', 'WPA3ENT192', 'OWE')]
        [System.String]
        $Authentication = 'WPA2PSK',

        [Parameter(ParameterSetName = 'UsingArguments')]
        [Parameter(ParameterSetName = 'UsingArgumentsWithEAP')]
        [ValidateSet('none', 'WEP', 'TKIP', 'AES', 'GCMP256')]
        [System.String]
        $Encryption = 'AES',

        [Parameter(ParameterSetName = 'UsingArguments')]
        [System.Security.SecureString]
        $Password,

        [Parameter(ParameterSetName = 'UsingArguments')]
        [Parameter(ParameterSetName = 'UsingArgumentsWithEAP')]
        [System.Boolean]
        $ConnectHiddenSSID = $false,

        [Parameter(ParameterSetName = 'UsingArgumentsWithEAP')]
        [ValidateSet('PEAP', 'TLS')]
        [System.String]
        $EAPType,

        [Parameter(ParameterSetName = 'UsingArgumentsWithEAP')]
        [AllowEmptyString()]
        [System.String]
        $ServerNames = '',

        [Parameter(ParameterSetName = 'UsingArgumentsWithEAP')]
        [System.String]
        $TrustedRootCA,

        [Parameter()]
        [System.String]
        $WiFiAdapterName,

        [Parameter(Mandatory = $true, ParameterSetName = 'UsingXml')]
        [System.String]
        $XmlProfile
    )

    New-WiFiProfile @PSBoundParameters -Overwrite $true
}

<#
    .SYNOPSIS
        Adds WifiAdapterName and InterfaceGuid to all the objects that are returned.
 
    .PARAMETER $InputObject
        The object the two properties will be added to.
 
    .PARAMETER InterfaceInfo
        The interfaceInfo object that the WiFiAdapterName and InterfaceGuid come from.
#>

function Add-DefaultProperty
{
    [CmdletBinding()]
    param
    (
        [Parameter(Mandatory)]
        [object]
        $InputObject,

        [Parameter(Mandatory)]
        [object]
        $InterfaceInfo
    )

    Add-Member -InputObject $InputObject -MemberType 'NoteProperty' -Name 'WiFiAdapterName' -Value $InterfaceInfo.Name -Force
    Add-Member -InputObject $InputObject -MemberType 'NoteProperty' -Name 'InterfaceGuid' -Value $InterfaceInfo.InterfaceGuid -Force

    if ($InputObject -is  [WiFi.ProfileManagement+WLAN_CONNECTION_ATTRIBUTES])
    {
        $apMac = [System.BitConverter]::ToString($InputObject.wlanAssociationAttributes._dot11Bssid)
        Add-Member -InputObject $InputObject -MemberType 'NoteProperty' -Name 'APMacAddress' -Value $apMac -Force
    }

    return $InputObject
}

function Format-BssEntry
{
    [CmdletBinding()]
    param
    (
        [Parameter(Mandatory)]
        [object]
        $BssEntry
    )

    $BssEntry | Select-Object -Property @(
        @{Label = 'SSID' ; Expression = {$_.dot11Ssid.ucSSID}}
        @{Label = 'PhyId'; Expression = {$_.phyId}}
        @{Label = 'APMacAddress'; Expression = {[System.BitConverter]::ToString($_.dot11Bssid)}}
        @{Label = 'Dot11BssType'; Expression = {$_.dot11BssType}}
        @{Label = 'RSSI'; Expression = {$_.rssi}}
        @{Label = 'LinkQuality'; Expression = {$_.linkQuality}}
        @{Label = 'InRegulatoryDomain'; Expression = {$_.inRegDomain}}
        @{Label = 'BeaconPeriod'; Expression = {$_.beaconPeriod}}
        @{Label = 'TimeStamp'; Expression = {$_.timestamp}}
        @{Label = 'HostTimeStamp'; Expression = {[datetime]::FromFileTime($_.hostTimestamp)}}
        @{Label = 'CapabilityInformation'; Expression = {$_.capabilityInformation}}
        @{Label = 'ChannelCenterFrequency'; Expression = {$_.chCenterFrequency}}
        @{Label = 'WlanRateSet'; Expression = {$_.wlanRateSet}}
        @{Label = 'IEOffset'; Expression = {$_.ieOffset}}
        @{Label = 'IESize'; Expression = {$_.ieSize}}
    )
}

<#
    .SYNOPSIS
        An internal function to format the reason code returned by WlanSetProfile
    .PARAMETER ReasonCode
        A value that indicates why the profile failed.
#>

function Format-WiFiReasonCode
{
    [OutputType([System.String])]
    [Cmdletbinding()]
    param
    (
        [Parameter()]
        [System.IntPtr]
        $ReasonCode
    )

    $stringBuilder = New-Object -TypeName Text.StringBuilder
    $stringBuilder.Capacity = 1024

    $result = [WiFi.ProfileManagement]::WlanReasonCodeToString(
        $ReasonCode.ToInt32(),
        $stringBuilder.Capacity,
        $stringBuilder,
        [IntPtr]::zero
    )

    if ($result -ne 0)
    {
        $errorMessage = Format-Win32Exception -ReturnCode $result
        Write-Error -Message ($script:localizedData.ErrorReasonCode -f $errorMessage)
    }

    return $stringBuilder.ToString()
}

<#
    .SYNOPSIS
        Returns the exception message from a Win32 API call
    .PARAMETER ReturnCode
        Specifies the return code that will be resolved to an error message.
#>

function Format-Win32Exception
{
    [OutputType([System.String])]
    [CmdletBinding()]
    param
    (
        [Parameter(Mandatory = $true)]
        [System.Int32]
        $ReturnCode
    )

    return [System.ComponentModel.Win32Exception]::new($ReturnCode).Message
}

#let return guid and adaptor description
function Get-InterfaceInfo
{
    [CmdletBinding()]
    param
    (
        [Parameter()]
        [System.String]
        $WiFiAdapterName
    )

    $result = @()
    $wifiAdapters = @()
    $getNetAdapterParams = @()

    $wifiInterfaces = Get-WiFiInterface

    if (!$WiFiAdapterName)
    {
        foreach ($wifiInterface in $wifiInterfaces)
        {
            $getNetAdapterParams +=@(
                 @{InterfaceDescription = $wifiInterface.Description}
            )
        }
    }
    else
    {
        $getNetAdapterParams = @(
            @{Name = $WiFiAdapterName}
        )
    }

    foreach ($getNetAdapterParam in $getNetAdapterParams)
    {
        $wifiAdapters = Get-NetAdapter @getNetAdapterParam
    }

    # ensure we are using wifi adaptors
    foreach ($wifiAdapter in $wifiAdapters)
    {
        if ($wifiAdapter.InterfaceGuid -notin $wifiInterfaces.Guid)
        {
            Write-Error -Message ($script:localizedData.ErrorNotWiFiAdapter -f $wifiAdapter.Name)
        }
        else
        {
            $result += $wifiAdapter
        }
    }

    if ($result.Count -eq 0)
    {
        throw $script:localizedData.ErrorNoWiFiAdaptersFound
    }
    return $result
}

<#
    .SYNOPSIS
        Lists the wireless interfaces and their state.
#>

function Get-WiFiInterface
{
    [CmdletBinding()]
    [OutputType([WiFi.ProfileManagement+WLAN_INTERFACE_INFO])]
    param ()

    $interfaceListPtr = 0
    $clientHandle = New-WiFiHandle

    try
    {
        [void] [WiFi.ProfileManagement]::WlanEnumInterfaces($clientHandle, [IntPtr]::zero, [ref] $interfaceListPtr)
        $wiFiInterfaceList = [WiFi.ProfileManagement+WLAN_INTERFACE_INFO_LIST]::new($interfaceListPtr)

        foreach ($wlanInterfaceInfo in $wiFiInterfaceList.wlanInterfaceInfo)
        {
            [WiFi.ProfileManagement+WLAN_INTERFACE_INFO] $wlanInterfaceInfo
        }
    }
    catch
    {
        Write-Error $PSItem
    }
    finally
    {
        Remove-WiFiHandle -ClientHandle $clientHandle
    }
}

<#
    .SYNOPSIS
        Retrieves the information of a WiFi profile.
    .PARAMETER ProfileName
        The name of the WiFi profile. Profile names are case-sensitive.
    .PARAMETER InterfaceGuid
        Specifies the Guid of the wireless network card. This is required by the native WiFi functions.
    .PARAMETER ClientHandle
        Specifies the handle used by the native WiFi functions.
    .PARAMETER WlanProfileFlags
        A pointer to the address location used to provide additional information about the request.
#>

function Get-WiFiProfileInfo
{
    [OutputType([System.Management.Automation.PSCustomObject])]
    [CmdletBinding()]
    param
    (
        [Parameter()]
        [System.String]
        $ProfileName,

        [Parameter()]
        [System.Guid]
        $InterfaceGuid,

        [Parameter()]
        [System.IntPtr]
        $ClientHandle,

        [System.Int16]
        $WlanProfileFlags
    )
    
    begin
    {
        [String] $pstrProfileXml = $null
        $wlanAccess = 0
        $WlanProfileFlagsInput = $WlanProfileFlags
    }
    process
    {
        $result = [WiFi.ProfileManagement]::WlanGetProfile(
            $ClientHandle,
            $InterfaceGuid,
            $ProfileName,
            [IntPtr]::Zero,
            [ref] $pstrProfileXml,
            [ref] $WlanProfileFlags,
            [ref] $wlanAccess
        )

        if ($result -ne 0)
        {
            $errorMessage = Format-Win32Exception -ReturnCode $result
            throw $($script:localizedData.ErrorGettingProfile -f $errorMessage)
        }

        $wlanProfile = [xml] $pstrProfileXml

        #Parse password
        if ($WlanProfileFlagsInput -eq 13)
        {
            $password = $wlanProfile.WLANProfile.MSM.security.sharedKey.keyMaterial
        }
        else
        {
            $password = $null
        }

        # Parse nonBroadcast flag
        if ([bool]::TryParse($wlanProfile.WLANProfile.SSIDConfig.nonBroadcast, [ref] $null))
        {
            $connectHiddenSSID = [bool]::Parse($wlanProfile.WLANProfile.SSIDConfig.nonBroadcast)
        }
        else
        {
            $connectHiddenSSID = $false
        }

        # Parse EAP type
        if ($wlanProfile.WLANProfile.MSM.security.authEncryption.useOneX -eq 'true')
        {
            switch ($wlanProfile.WLANProfile.MSM.security.OneX.EAPConfig.EapHostConfig.EapMethod.Type.InnerText)
            {
                '25'    #EAP-PEAP (MSCHAPv2)
                {
                    $eapType = 'PEAP'
                }

                '13'    #EAP-TLS
                {
                    $eapType = 'TLS'
                }

                Default
                {
                    $eapType = 'Unknown'
                }
            }
        }
        else
        {
            $eapType = $null
        }

        # Parse Validation Server Name
        if ($null -ne $eapType)
        {
            switch ($eapType)
            {
                'PEAP'
                { 
                    $serverNames = $wlanProfile.WLANProfile.MSM.security.OneX.EAPConfig.EapHostConfig.Config.Eap.EapType.ServerValidation.ServerNames
                }

                'TLS'
                {
                    $node = $wlanProfile.WLANProfile.MSM.security.OneX.EAPConfig.EapHostConfig.Config.SelectNodes("//*[local-name()='ServerNames']")
                    $serverNames = $node[0].InnerText
                }
            }
        }

        # Parse Validation TrustedRootCA
        if ($null -ne $eapType)
        {
            switch ($eapType)
            {
                'PEAP'
                { 
                    $trustedRootCa = ([string] ($wlanProfile.WLANProfile.MSM.security.OneX.EAPConfig.EapHostConfig.Config.Eap.EapType.ServerValidation.TrustedRootCA -replace ' ', [string]::Empty)).ToLower()
                }

                'TLS'
                {
                    $node = $wlanProfile.WLANProfile.MSM.security.OneX.EAPConfig.EapHostConfig.Config.SelectNodes("//*[local-name()='TrustedRootCA']")
                    $trustedRootCa = ([string] ($node[0].InnerText -replace ' ', [string]::Empty)).ToLower()
                }
            }
        }


        [WiFi.ProfileManagement+ProfileInfo]@{
            ProfileName       = $wlanProfile.WLANProfile.SSIDConfig.SSID.name
            ConnectionMode    = $wlanProfile.WLANProfile.connectionMode
            Authentication    = $wlanProfile.WLANProfile.MSM.security.authEncryption.authentication
            Encryption        = $wlanProfile.WLANProfile.MSM.security.authEncryption.encryption
            Password          = $password
            ConnectHiddenSSID = $connectHiddenSSID
            EAPType           = $eapType
            ServerNames       = $serverNames
            TrustedRootCA     = $trustedRootCa
            Xml               = $pstrProfileXml
        }
    }
    end
    {
        $xmlPtr = [System.Runtime.InteropServices.Marshal]::StringToHGlobalAuto($pstrProfileXml)
        Invoke-WlanFreeMemory -Pointer $xmlPtr
    }
}

<#
    .SYNOPSIS
        Call the WlanConnect function to attempt to connect to a specific network
    .PARAMETER InterfaceGuid
        Specifies the Guid of the wireless network card. This is required by the native WiFi functions.
    .PARAMETER ClientHandle
        Specifies the handle used by the native WiFi functions.
    .PARAMETER ConnectionParameterList
        A WLAN_CONNECTION_PARAMETERS structure that specifies the parameters used when using the WlanConnect function.
#>

function Invoke-WlanConnect
{
    [OutputType([void])]
    [CmdletBinding()]
    param
    (
        [Parameter(Mandatory = $true)]
        [System.IntPtr]
        $ClientHandle,

        [Parameter(Mandatory = $true)]
        [System.Guid]
        $InterfaceGuid,

        [Parameter(Mandatory = $true)]
        [WiFi.ProfileManagement+WLAN_CONNECTION_PARAMETERS]
        $ConnectionParameterList
    )

    $result = [WiFi.ProfileManagement]::WlanConnect(
        $ClientHandle,
        [ref] $InterfaceGuid,
        [ref] $ConnectionParameterList,
        [IntPtr]::Zero
    )

    if ($result -ne 0)
    {
        $errorMessage = Format-Win32Exception -ReturnCode $result
        throw $($script:localizedData.ErrorWlanConnect -f $ConnectionParameterList.strProfile, $errorMessage)
    }
    else
    {
        Write-Verbose -Message $($script:localizedData.SuccessWlanConnect -f $ConnectionParameterList.strProfile, $errorMessage)
    }
}

<#
    .SYNOPSIS
        Frees memory used by Native WiFi functions
    .PARAMETER Pointer
        Pointer to the memory to be freed.
#>

function Invoke-WlanFreeMemory
{
    [OutputType([void])]
    [CmdletBinding()]
    param
    (
        [Parameter(Mandatory = $true)]
        [System.IntPtr[]]
        $Pointer
    )

    foreach ($ptr in $Pointer)
    {
        if ($ptr -ne 0)
        {
            try
            {
                [WiFi.ProfileManagement]::WlanFreeMemory($ptr)
            }
            catch
            {
                throw $($script:localizedData.ErrorFreeMemory -f $errorMessage)
            }
        }
    }
}

<#
    .SYNOPSIS
        Creates a WLAN_CONNECTION_PARAMETERS structure that contains the required parameters when using the WlanConnect function
    .PARAMETER ProfileName
        The name of the profile to connect to. Profile names are case-sensitive.
    .PARAMETER ConnectionMode
        Specifies the mode of connection. Valid values are 'Profile', 'TemporaryProfile', 'DiscoverySecure', 'DiscoveryUnsecure', 'Auto'
    .PARAMETER Dot11BssType
        A value that indicates the BSS type of the network. If a profile is provided, this BSS type must be the same as the one in the profile.
    .NOTES
        https://msdn.microsoft.com/en-us/library/windows/desktop/ms706851(v=vs.85).aspx
#>

function New-WiFiConnectionParameter
{
    [OutputType([WiFi.ProfileManagement+WLAN_CONNECTION_PARAMETERS])]
    [CmdletBinding()]
    param
    (
        [Parameter(Mandatory = $true)]
        [System.String]
        $ProfileName,

        [Parameter()]
        [ValidateSet('Profile', 'TemporaryProfile', 'DiscoverySecure', 'DiscoveryUnsecure', 'Auto')]
        [System.String]
        $ConnectionMode = 'Profile',

        [Parameter()]
        [ValidateSet('Any', 'Independent', 'Infrastructure')]
        [WiFi.ProfileManagement+DOT11_BSS_TYPE]
        $Dot11BssType = 'Any',

        [Parameter()]
        [WiFi.ProfileManagement+WlanConnectionFlag]
        $Flag = 'Default'
    )

    try
    {
        #region resolvers
        $connectionModeResolver = @{
            Profile           = 'wlan_connection_mode_profile'
            TemporaryProfile  = 'wlan_connection_mode_temporary_profile'
            DiscoverySecure   = 'wlan_connection_mode_discovery_secure'
            DiscoveryUnsecure = 'wlan_connection_mode_discovery_unsecure'
            Auto              = 'wlan_connection_mode_auto'
        }
        #endregion

        $connectionParameters = [WiFi.ProfileManagement+WLAN_CONNECTION_PARAMETERS]::new()
        $connectionParameters.strProfile = $ProfileName
        $connectionParameters.wlanConnectionMode = [WiFi.ProfileManagement+WLAN_CONNECTION_MODE]::$($connectionModeResolver[$ConnectionMode])
        $connectionParameters.dot11BssType = [WiFi.ProfileManagement+DOT11_BSS_TYPE]::$Dot11BssType
        $connectionParameters.dwFlags = [WiFi.ProfileManagement+WlanConnectionFlag]::$Flag
    }
    catch
    {
        throw $PSItem
    }

    return $connectionParameters
}

<#
    .SYNOPSIS
        Opens a WiFi handle
#>

function New-WiFiHandle
{    
    [CmdletBinding()]
    [OutputType([System.IntPtr])]
    param()

    $maxClient = 2
    [Ref]$negotiatedVersion = 0
    $clientHandle = [IntPtr]::zero

    $result = [WiFi.ProfileManagement]::WlanOpenHandle(
        $maxClient,
        [IntPtr]::Zero,
        $negotiatedVersion,
        [ref] $clientHandle
    )
    
    if ($result -eq 0)
    {
        return $clientHandle
    }
    else
    {
        $errorMessage = Format-Win32Exception -ReturnCode $result
        throw $($script:localizedData.ErrorOpeningHandle -f $errorMessage)
    }
}

$script:WiFiProfileXmlPersonal = @"
<?xml version="1.0"?>
<WLANProfile xmlns="http://www.microsoft.com/networking/WLAN/profile/v1">
  <name>{0}</name>
  <SSIDConfig>
    <SSID>
      <name>{0}</name>
    </SSID>
    <nonBroadcast>{1}</nonBroadcast>
  </SSIDConfig>
  <connectionType>ESS</connectionType>
  <connectionMode>{2}</connectionMode>
  <MSM>
    <security>
      <authEncryption>
        <authentication>{3}</authentication>
        <encryption>{4}</encryption>
        <useOneX>false</useOneX>
      </authEncryption>
      <sharedKey>
        <keyType>passPhrase</keyType>
        <protected>false</protected>
        <keyMaterial>{5}</keyMaterial>
      </sharedKey>
    </security>
  </MSM>
</WLANProfile>
"@


$script:WiFiProfileXmlEapPeap = @"
<?xml version="1.0"?>
<WLANProfile xmlns="http://www.microsoft.com/networking/WLAN/profile/v1">
  <name>{0}</name>
  <SSIDConfig>
    <SSID>
      <name>{0}</name>
    </SSID>
    <nonBroadcast>{1}</nonBroadcast>
  </SSIDConfig>
  <connectionType>ESS</connectionType>
  <connectionMode>{2}</connectionMode>
  <MSM>
    <security>
      <authEncryption>
        <authentication>{3}</authentication>
        <encryption>{4}</encryption>
        <useOneX>true</useOneX>
      </authEncryption>
      <PMKCacheMode>enabled</PMKCacheMode>
      <PMKCacheTTL>720</PMKCacheTTL>
      <PMKCacheSize>128</PMKCacheSize>
      <preAuthMode>disabled</preAuthMode>
      <OneX xmlns="http://www.microsoft.com/networking/OneX/v1">
        <authMode>machineOrUser</authMode>
        <EAPConfig>
          <EapHostConfig xmlns="http://www.microsoft.com/provisioning/EapHostConfig">
            <EapMethod>
              <Type xmlns="http://www.microsoft.com/provisioning/EapCommon">25</Type>
              <VendorId xmlns="http://www.microsoft.com/provisioning/EapCommon">0</VendorId>
              <VendorType xmlns="http://www.microsoft.com/provisioning/EapCommon">0</VendorType>
              <AuthorId xmlns="http://www.microsoft.com/provisioning/EapCommon">0</AuthorId>
            </EapMethod>
            <Config xmlns="http://www.microsoft.com/provisioning/EapHostConfig">
              <Eap xmlns="http://www.microsoft.com/provisioning/BaseEapConnectionPropertiesV1">
                <Type>25</Type>
                <EapType xmlns="http://www.microsoft.com/provisioning/MsPeapConnectionPropertiesV1">
                  <ServerValidation>
                    <DisableUserPromptForServerValidation>false</DisableUserPromptForServerValidation>
                    <ServerNames></ServerNames>
                    <TrustedRootCA></TrustedRootCA>
                  </ServerValidation>
                  <FastReconnect>true</FastReconnect>
                  <InnerEapOptional>false</InnerEapOptional>
                  <Eap xmlns="http://www.microsoft.com/provisioning/BaseEapConnectionPropertiesV1">
                    <Type>26</Type>
                    <EapType xmlns="http://www.microsoft.com/provisioning/MsChapV2ConnectionPropertiesV1">
                      <UseWinLogonCredentials>false</UseWinLogonCredentials>
                    </EapType>
                  </Eap>
                  <EnableQuarantineChecks>false</EnableQuarantineChecks>
                  <RequireCryptoBinding>false</RequireCryptoBinding>
                  <PeapExtensions>
                    <PerformServerValidation xmlns="http://www.microsoft.com/provisioning/MsPeapConnectionPropertiesV2">true</PerformServerValidation>
                    <AcceptServerName xmlns="http://www.microsoft.com/provisioning/MsPeapConnectionPropertiesV2">true</AcceptServerName>
                    <PeapExtensionsV2 xmlns="http://www.microsoft.com/provisioning/MsPeapConnectionPropertiesV2">
                      <AllowPromptingWhenServerCANotFound xmlns="http://www.microsoft.com/provisioning/MsPeapConnectionPropertiesV3">true</AllowPromptingWhenServerCANotFound>
                    </PeapExtensionsV2>
                  </PeapExtensions>
                </EapType>
              </Eap>
            </Config>
          </EapHostConfig>
        </EAPConfig>
      </OneX>
    </security>
  </MSM>
</WLANProfile>
"@


$script:WiFiProfileXmlEapTls = @"
<?xml version="1.0"?>
<WLANProfile xmlns="http://www.microsoft.com/networking/WLAN/profile/v1">
  <name>{0}</name>
  <SSIDConfig>
    <SSID>
      <name>{0}</name>
    </SSID>
    <nonBroadcast>{1}</nonBroadcast>
  </SSIDConfig>
  <connectionType>ESS</connectionType>
  <connectionMode>{2}</connectionMode>
  <MSM>
    <security>
      <authEncryption>
        <authentication>{3}</authentication>
        <encryption>{4}</encryption>
        <useOneX>true</useOneX>
      </authEncryption>
      <PMKCacheMode>enabled</PMKCacheMode>
      <PMKCacheTTL>720</PMKCacheTTL>
      <PMKCacheSize>128</PMKCacheSize>
      <preAuthMode>disabled</preAuthMode>
      <OneX xmlns="http://www.microsoft.com/networking/OneX/v1">
        <authMode>machineOrUser</authMode>
        <EAPConfig>
          <EapHostConfig xmlns="http://www.microsoft.com/provisioning/EapHostConfig">
            <EapMethod>
              <Type xmlns="http://www.microsoft.com/provisioning/EapCommon">13</Type>
              <VendorId xmlns="http://www.microsoft.com/provisioning/EapCommon">0</VendorId>
              <VendorType xmlns="http://www.microsoft.com/provisioning/EapCommon">0</VendorType>
              <AuthorId xmlns="http://www.microsoft.com/provisioning/EapCommon">0</AuthorId>
            </EapMethod>
            <Config xmlns:baseEap="http://www.microsoft.com/provisioning/BaseEapConnectionPropertiesV1" xmlns:eapTls="http://www.microsoft.com/provisioning/EapTlsConnectionPropertiesV1">
              <baseEap:Eap>
                <baseEap:Type>13</baseEap:Type>
                <eapTls:EapType>
                  <eapTls:CredentialsSource>
                    <eapTls:CertificateStore />
                  </eapTls:CredentialsSource>
                  <eapTls:ServerValidation>
                    <eapTls:DisableUserPromptForServerValidation>false</eapTls:DisableUserPromptForServerValidation>
                    <eapTls:ServerNames></eapTls:ServerNames>
                    <eapTls:TrustedRootCA></eapTls:TrustedRootCA>
                  </eapTls:ServerValidation>
                  <eapTls:DifferentUsername>false</eapTls:DifferentUsername>
                </eapTls:EapType>
              </baseEap:Eap>
            </Config>
          </EapHostConfig>
        </EAPConfig>
      </OneX>
    </security>
  </MSM>
</WLANProfile>
"@



<#
    .SYNOPSIS
        Create a string of XML that represents the wireless profile.
    .PARAMETER ProfileName
        The name of the wireless profile to be updated. Profile names are case sensitive.
    .PARAMETER ConnectionMode
        Indicates whether connection to the wireless LAN should be automatic ("auto") or initiated ("manual") by user.
    .PARAMETER Authentication
        Specifies the authentication method to be used to connect to the wireless LAN.
    .PARAMETER Encryption
        Sets the data encryption to use to connect to the wireless LAN.
#>

function New-WiFiProfileXml
{
    [OutputType([System.String])]
    [CmdletBinding()]
    param
    (
        [Parameter(Mandatory = $true, Position = 0)]
        [System.String]
        $ProfileName,

        [Parameter()]
        [ValidateSet('manual', 'auto')]
        [System.String]
        $ConnectionMode = 'auto',

        [Parameter()]
        [System.String]
        $Authentication = 'WPA2PSK',

        [Parameter()]
        [System.String]
        $Encryption = 'AES',

        [Parameter()]
        [System.Security.SecureString]
        $Password,

        [Parameter()]
        [System.Boolean]
        $ConnectHiddenSSID = $false,

        [Parameter()]
        [System.String]
        $EAPType,

        [Parameter()]
        [AllowEmptyString()]
        [System.String]
        $ServerNames = '',

        [Parameter()]
        [System.String]
        $TrustedRootCA
    )

    try
    {
        if ($Password)
        {
            $secureStringToBstr = [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($Password)
            $plainPassword = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto($secureStringToBstr)
        }

        if ($EAPType -eq 'PEAP')
        {
            $profileXml = [xml] ($script:WiFiProfileXmlEapPeap -f $ProfileName, ([string] $ConnectHiddenSSID).ToLower(), $ConnectionMode, $Authentication, $Encryption)

            if ($ServerNames)
            {
                $profileXml.WLANProfile.MSM.security.OneX.EAPConfig.EapHostConfig.Config.Eap.EapType.ServerValidation.ServerNames = $ServerNames
            }

            if ($TrustedRootCA)
            {
                [string]$formattedCaHash = $TrustedRootCA -replace '..', '$& '
                $profileXml.WLANProfile.MSM.security.OneX.EAPConfig.EapHostConfig.Config.Eap.EapType.ServerValidation.TrustedRootCA = $formattedCaHash
            }
        }
        elseif ($EAPType -eq 'TLS')
        {
            $profileXml = [xml] ($script:WiFiProfileXmlEapTls -f $ProfileName, ([string] $ConnectHiddenSSID).ToLower(), $ConnectionMode, $Authentication, $Encryption)

            if ($ServerNames)
            {
                $node = $profileXml.WLANProfile.MSM.security.OneX.EAPConfig.EapHostConfig.Config.SelectNodes("//*[local-name()='ServerNames']")
                $node[0].InnerText = $ServerNames
            }

            if ($TrustedRootCA)
            {
                [string]$formattedCaHash = $TrustedRootCA -replace '..', '$& '
                $node = $profileXml.WLANProfile.MSM.security.OneX.EAPConfig.EapHostConfig.Config.SelectNodes("//*[local-name()='TrustedRootCA']")
                $node[0].InnerText = $formattedCaHash
            }
        }
        else
        {
            $profileXml = [xml] ($script:WiFiProfileXmlPersonal -f $ProfileName, ([string] $ConnectHiddenSSID).ToLower(), $ConnectionMode, $Authentication, $Encryption, $plainPassword)
            if (-not $plainPassword)
            {
                $null = $profileXml.WLANProfile.MSM.security.RemoveChild($profileXml.WLANProfile.MSM.security.sharedKey)
            }

            if ($Authentication -eq 'WPA3SAE'){
              # Set transition mode as true for WPA3-SAE
              $nsmg = [System.Xml.XmlNamespaceManager]::new($profileXml.NameTable)
              $nsmg.AddNamespace('WLANProfile', $profileXml.DocumentElement.GetAttribute('xmlns'))
              $refNode = $profileXml.SelectSingleNode('//WLANProfile:authEncryption', $nsmg)
              $xmlnode = $profileXml.CreateElement('transitionMode', 'http://www.microsoft.com/networking/WLAN/profile/v4')
              $xmlnode.InnerText = 'true'
              $null = $refNode.AppendChild($xmlnode)
            }
        }

        $profileXml.OuterXml
    }
    catch
    {
        throw $PSItem
    }
}

<#
    .SYNOPSIS
        Closes an open WiFi handle
    .Parameter ClientHandle
        Specifies the object that represents the open WiFi handle.
#>

function Remove-WiFiHandle
{
    [OutputType([void])]
    [CmdletBinding()]
    param
    (
        [Parameter()]
        [System.IntPtr]
        $ClientHandle
    )

    $result = [WiFi.ProfileManagement]::WlanCloseHandle($ClientHandle, [IntPtr]::zero)

    if ($result -eq 0)
    {
        Write-Verbose -Message ($script:localizedData.HandleClosed)
    }
    else
    {
        $errorMessage = Format-Win32Exception -ReturnCode $result
        throw $($script:localizedData.ErrorClosingHandle -f $errorMessage)
    }
}

# https://github.com/2dpodcast/ManagedNativeWifi/blob/281299931af60fcca84149939de4acceb4133820/ManagedNativeWifi/Win32/NativeMethod.cs
$WlanGetProfileListSig = @'
 
[DllImport("wlanapi.dll")]
public static extern uint WlanOpenHandle(
    [In] UInt32 clientVersion,
    [In, Out] IntPtr pReserved,
    [Out] out UInt32 negotiatedVersion,
    [Out] out IntPtr clientHandle
);
 
[DllImport("Wlanapi.dll")]
public static extern uint WlanCloseHandle(
    [In] IntPtr ClientHandle,
    IntPtr pReserved
);
 
[DllImport("wlanapi.dll", SetLastError = true, CallingConvention=CallingConvention.Winapi)]
public static extern uint WlanGetProfileList(
    [In] IntPtr clientHandle,
    [In, MarshalAs(UnmanagedType.LPStruct)] Guid interfaceGuid,
    [In] IntPtr pReserved,
    [Out] out IntPtr profileList
);
 
[DllImport("wlanapi.dll")]
public static extern uint WlanGetProfile(
    [In]IntPtr clientHandle,
    [In, MarshalAs(UnmanagedType.LPStruct)] Guid interfaceGuid,
    [In, MarshalAs(UnmanagedType.LPWStr)] string profileName,
    [In, Out] IntPtr pReserved,
    [Out, MarshalAs(UnmanagedType.LPWStr)] out string pstrProfileXml,
    [In, Out, Optional] ref uint flags,
    [Out, Optional] out uint grantedAccess
);
 
[DllImport("wlanapi.dll")]
public static extern uint WlanDeleteProfile(
    [In]IntPtr clientHanle,
    [In, MarshalAs(UnmanagedType.LPStruct)] Guid interfaceGuid,
    [In, MarshalAs(UnmanagedType.LPWStr)] string profileName,
    [In, Out] IntPtr pReserved
);
 
[DllImport("wlanapi.dll", EntryPoint = "WlanFreeMemory")]
public static extern void WlanFreeMemory(
    [In] IntPtr pMemory
);
 
[DllImport("Wlanapi.dll", SetLastError = true, CharSet = CharSet.Unicode)]
public static extern uint WlanSetProfile(
    [In] IntPtr clientHanle,
    [In] ref Guid interfaceGuid,
    [In] uint flags,
    [In] IntPtr ProfileXml,
    [In, Optional] IntPtr AllUserProfileSecurity,
    [In] bool Overwrite,
    [In, Out] IntPtr pReserved,
    [In, Out]ref IntPtr pdwReasonCode
);
 
[DllImport("wlanapi.dll",SetLastError = true, CharSet = CharSet.Unicode)]
public static extern uint WlanReasonCodeToString(
    [In] uint reasonCode,
    [In] uint bufferSize,
    [In, Out] StringBuilder builder,
    [In, Out] IntPtr Reserved
);
 
[DllImport("Wlanapi.dll", SetLastError = true)]
public static extern uint WlanGetAvailableNetworkList(
    [In] IntPtr hClientHandle,
    [In, MarshalAs(UnmanagedType.LPStruct)] Guid interfaceGuid,
    [In] uint dwFlags,
    [In] IntPtr pReserved,
    [Out] out IntPtr ppAvailableNetworkList
);
 
[DllImport("Wlanapi.dll", SetLastError = true)]
public static extern uint WlanConnect(
    [In] IntPtr hClientHandle,
    [In] ref Guid interfaceGuid,
    [In] ref WLAN_CONNECTION_PARAMETERS pConnectionParameters,
    [In, Out] IntPtr pReserved
);
 
[StructLayout(LayoutKind.Sequential,CharSet=CharSet.Unicode)]
public struct WLAN_CONNECTION_PARAMETERS
{
    public WLAN_CONNECTION_MODE wlanConnectionMode;
    public string strProfile;
    public DOT11_SSID[] pDot11Ssid;
    public DOT11_BSSID_LIST[] pDesiredBssidList;
    public DOT11_BSS_TYPE dot11BssType;
    public uint dwFlags;
}
 
public struct DOT11_BSSID_LIST
{
    public NDIS_OBJECT_HEADER Header;
    public ulong uNumOfEntries;
    public ulong uTotalNumOfEntries;
    public IntPtr BSSIDs;
}
 
public struct NDIS_OBJECT_HEADER
{
    public byte Type;
    public byte Revision;
    public ushort Size;
}
 
public struct WLAN_PROFILE_INFO_LIST
{
    public uint dwNumberOfItems;
    public uint dwIndex;
    public WLAN_PROFILE_INFO[] ProfileInfo;
 
    public WLAN_PROFILE_INFO_LIST(IntPtr ppProfileList)
    {
        dwNumberOfItems = (uint)Marshal.ReadInt32(ppProfileList);
        dwIndex = (uint)Marshal.ReadInt32(ppProfileList, 4);
        ProfileInfo = new WLAN_PROFILE_INFO[dwNumberOfItems];
        IntPtr ppProfileListTemp = new IntPtr(ppProfileList.ToInt64() + 8);
 
        for (int i = 0; i < dwNumberOfItems; i++)
        {
            ppProfileList = new IntPtr(ppProfileListTemp.ToInt64() + i * Marshal.SizeOf(typeof(WLAN_PROFILE_INFO)));
            ProfileInfo[i] = (WLAN_PROFILE_INFO)Marshal.PtrToStructure(ppProfileList, typeof(WLAN_PROFILE_INFO));
        }
    }
}
 
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
public struct WLAN_PROFILE_INFO
{
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 256)]
    public string strProfileName;
    public WlanProfileFlags ProfileFLags;
}
 
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
public struct WLAN_AVAILABLE_NETWORK_LIST
{
    public uint dwNumberOfItems;
    public uint dwIndex;
    public WLAN_AVAILABLE_NETWORK[] wlanAvailableNetwork;
    public WLAN_AVAILABLE_NETWORK_LIST(IntPtr ppAvailableNetworkList)
    {
        dwNumberOfItems = (uint)Marshal.ReadInt64 (ppAvailableNetworkList);
        dwIndex = (uint)Marshal.ReadInt64 (ppAvailableNetworkList, 4);
        wlanAvailableNetwork = new WLAN_AVAILABLE_NETWORK[dwNumberOfItems];
        for (int i = 0; i < dwNumberOfItems; i++)
        {
            IntPtr pWlanAvailableNetwork = new IntPtr (ppAvailableNetworkList.ToInt64() + i * Marshal.SizeOf (typeof(WLAN_AVAILABLE_NETWORK)) + 8);
            wlanAvailableNetwork[i] = (WLAN_AVAILABLE_NETWORK)Marshal.PtrToStructure (pWlanAvailableNetwork, typeof(WLAN_AVAILABLE_NETWORK));
        }
    }
}
 
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
public struct WLAN_AVAILABLE_NETWORK
{
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 256)]
    public string ProfileName;
    public DOT11_SSID Dot11Ssid;
    public DOT11_BSS_TYPE dot11BssType;
    public uint uNumberOfBssids;
    public bool bNetworkConnectable;
    public uint wlanNotConnectableReason;
    public uint uNumberOfPhyTypes;
 
    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)]
    public DOT11_PHY_TYPE[] dot11PhyTypes;
    public bool bMorePhyTypes;
    public uint SignalQuality;
    public bool SecurityEnabled;
    public DOT11_AUTH_ALGORITHM dot11DefaultAuthAlgorithm;
    public DOT11_CIPHER_ALGORITHM dot11DefaultCipherAlgorithm;
    public uint dwFlags;
    public uint dwReserved;
}
 
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
public struct DOT11_SSID
{
    /// ULONG->unsigned int
    public uint uSSIDLength;
 
    /// UCHAR[]
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)]
    public string ucSSID;
}
 
public enum DOT11_BSS_TYPE
{
    Infrastructure = 1,
    Independent = 2,
    Any = 3,
}
 
public enum DOT11_PHY_TYPE
{
    dot11_phy_type_unknown = 0,
    dot11_phy_type_any = 0,
    dot11_phy_type_fhss = 1,
    dot11_phy_type_dsss = 2,
    dot11_phy_type_irbaseband = 3,
    dot11_phy_type_ofdm = 4,
    dot11_phy_type_hrdsss = 5,
    dot11_phy_type_erp = 6,
    dot11_phy_type_ht = 7,
    dot11_phy_type_vht = 8,
    dot11_phy_type_IHV_start = -2147483648,
    dot11_phy_type_IHV_end = -1,
}
 
public enum DOT11_AUTH_ALGORITHM
{
    DOT11_AUTH_ALGO_80211_OPEN = 1,
    DOT11_AUTH_ALGO_80211_SHARED_KEY = 2,
    DOT11_AUTH_ALGO_WPA = 3,
    DOT11_AUTH_ALGO_WPA_PSK = 4,
    DOT11_AUTH_ALGO_WPA_NONE = 5,
    DOT11_AUTH_ALGO_RSNA = 6,
    DOT11_AUTH_ALGO_RSNA_PSK = 7,
    DOT11_AUTH_ALGO_WPA3 = 8,
    DOT11_AUTH_ALGO_WPA3_SAE = 9,
    DOT11_AUTH_ALGO_OWE = 10,
    DOT11_AUTH_ALGO_WPA3_ENT = 11,
    DOT11_AUTH_ALGO_IHV_START = -2147483648,
    DOT11_AUTH_ALGO_IHV_END = -1,
}
 
public enum DOT11_CIPHER_ALGORITHM
{
    /// DOT11_CIPHER_ALGO_NONE -> 0x00
    DOT11_CIPHER_ALGO_NONE = 0,
 
    /// DOT11_CIPHER_ALGO_WEP40 -> 0x01
    DOT11_CIPHER_ALGO_WEP40 = 1,
 
    /// DOT11_CIPHER_ALGO_TKIP -> 0x02
    DOT11_CIPHER_ALGO_TKIP = 2,
 
    /// DOT11_CIPHER_ALGO_CCMP -> 0x04
    DOT11_CIPHER_ALGO_CCMP = 4,
 
    /// DOT11_CIPHER_ALGO_WEP104 -> 0x05
    DOT11_CIPHER_ALGO_WEP104 = 5,
 
    /// DOT11_CIPHER_ALGO_BIP -> 0x06
    DOT11_CIPHER_ALGO_BIP = 6,
 
    /// DOT11_CIPHER_ALGO_GCMP -> 0x08
    DOT11_CIPHER_ALGO_GCMP = 8,
 
    /// DOT11_CIPHER_ALGO_GCMP_256 -> 0x09
    DOT11_CIPHER_ALGO_GCMP_256 = 9,
 
    /// DOT11_CIPHER_ALGO_CCMP_256 -> 0x0a
    DOT11_CIPHER_ALGO_CCMP_256 = 10,
 
    /// DOT11_CIPHER_ALGO_BIP_GMAC_128 -> 0x0b
    DOT11_CIPHER_ALGO_BIP_GMAC_128 = 11,
 
    /// DOT11_CIPHER_ALGO_BIP_GMAC_256 -> 0x0c
    DOT11_CIPHER_ALGO_BIP_GMAC_256 = 12,
 
    /// DOT11_CIPHER_ALGO_BIP_CMAC_256 -> 0x0d
    DOT11_CIPHER_ALGO_BIP_CMAC_256 = 13,
 
    /// DOT11_CIPHER_ALGO_WPA_USE_GROUP -> 0x100
    DOT11_CIPHER_ALGO_WPA_USE_GROUP = 256,
 
    /// DOT11_CIPHER_ALGO_RSN_USE_GROUP -> 0x100
    DOT11_CIPHER_ALGO_RSN_USE_GROUP = 256,
 
    /// DOT11_CIPHER_ALGO_WEP -> 0x101
    DOT11_CIPHER_ALGO_WEP = 257,
 
    /// DOT11_CIPHER_ALGO_IHV_START -> 0x80000000
    DOT11_CIPHER_ALGO_IHV_START = -2147483648,
 
    /// DOT11_CIPHER_ALGO_IHV_END -> 0xffffffff
    DOT11_CIPHER_ALGO_IHV_END = -1,
}
 
public enum WLAN_CONNECTION_MODE
{
    wlan_connection_mode_profile,
    wlan_connection_mode_temporary_profile,
    wlan_connection_mode_discovery_secure,
    wlan_connection_mode_discovery_unsecure,
    wlan_connection_mode_auto,
    wlan_connection_mode_invalid,
}
 
[Flags]
public enum WlanConnectionFlag
{
    Default = 0,
    HiddenNetwork = 1,
    AdhocJoinOnly = 2,
    IgnorePrivayBit = 4,
    EapolPassThrough = 8,
    PersistDiscoveryProfile = 10,
    PersistDiscoveryProfileConnectionModeAuto = 20,
    PersistDiscoveryProfileOverwriteExisting = 40
}
 
[Flags]
public enum WlanProfileFlags
{
    AllUser = 0,
    GroupPolicy = 1,
    User = 2
}
 
public class ProfileInfo
{
    public string ProfileName;
    public string ConnectionMode;
    public string Authentication;
    public string Encryption;
    public string Password;
    public bool ConnectHiddenSSID;
    public string EAPType;
    public string ServerNames;
    public string TrustedRootCA;
    public string Xml;
}
 
[DllImport("Wlanapi.dll", SetLastError = true)]
public static extern uint WlanEnumInterfaces (
    [In] IntPtr hClientHandle,
    [In] IntPtr pReserved,
    [Out] out IntPtr ppInterfaceList
);
 
public struct WLAN_INTERFACE_INFO_LIST
{
    public uint dwNumberOfItems;
    public uint dwIndex;
    public WLAN_INTERFACE_INFO[] wlanInterfaceInfo;
    public WLAN_INTERFACE_INFO_LIST(IntPtr ppInterfaceInfoList)
    {
        dwNumberOfItems = (uint)Marshal.ReadInt32(ppInterfaceInfoList);
        dwIndex = (uint)Marshal.ReadInt32(ppInterfaceInfoList, 4);
        wlanInterfaceInfo = new WLAN_INTERFACE_INFO[dwNumberOfItems];
        IntPtr ppInterfaceInfoListTemp = new IntPtr(ppInterfaceInfoList.ToInt64() + 8);
        for (int i = 0; i < dwNumberOfItems; i++)
        {
            ppInterfaceInfoList = new IntPtr(ppInterfaceInfoListTemp.ToInt64() + i * Marshal.SizeOf(typeof(WLAN_INTERFACE_INFO)));
            wlanInterfaceInfo[i] = (WLAN_INTERFACE_INFO)Marshal.PtrToStructure(ppInterfaceInfoList, typeof(WLAN_INTERFACE_INFO));
        }
    }
}
 
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
public struct WLAN_INTERFACE_INFO
{
    public Guid Guid;
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 256)]
    public string Description;
    public WLAN_INTERFACE_STATE State;
}
 
public enum WLAN_INTERFACE_STATE {
    not_ready = 0,
    connected = 1,
    ad_hoc_network_formed = 2,
    disconnecting = 3,
    disconnected = 4,
    associating = 5,
    discovering = 6,
    authenticating = 7
}
 
[DllImport("Wlanapi.dll",SetLastError=true)]
public static extern uint WlanScan(
    IntPtr hClientHandle,
    ref Guid pInterfaceGuid,
    IntPtr pDot11Ssid,
    IntPtr pIeData,
    IntPtr pReserved
);
 
[DllImport("Wlanapi.dll")]
public static extern uint WlanSetInterface(
    IntPtr hClientHandle,
    ref Guid pInterfaceGuid,
    WLAN_INTF_OPCODE OpCode,
    uint dwDataSize,
    IntPtr pData ,
    IntPtr pReserved
);
 
public enum WLAN_INTF_OPCODE
{
    /// wlan_intf_opcode_autoconf_start -> 0x000000000
    wlan_intf_opcode_autoconf_start = 0,
 
    wlan_intf_opcode_autoconf_enabled,
 
    wlan_intf_opcode_background_scan_enabled,
 
    wlan_intf_opcode_media_streaming_mode,
 
    wlan_intf_opcode_radio_state,
 
    wlan_intf_opcode_bss_type,
 
    wlan_intf_opcode_interface_state,
 
    wlan_intf_opcode_current_connection,
 
    wlan_intf_opcode_channel_number,
 
    wlan_intf_opcode_supported_infrastructure_auth_cipher_pairs,
 
    wlan_intf_opcode_supported_adhoc_auth_cipher_pairs,
 
    wlan_intf_opcode_supported_country_or_region_string_list,
 
    wlan_intf_opcode_current_operation_mode,
 
    wlan_intf_opcode_supported_safe_mode,
 
    wlan_intf_opcode_certified_safe_mode,
 
    /// wlan_intf_opcode_autoconf_end -> 0x0fffffff
    wlan_intf_opcode_autoconf_end = 268435455,
 
    /// wlan_intf_opcode_msm_start -> 0x10000100
    wlan_intf_opcode_msm_start = 268435712,
 
    wlan_intf_opcode_statistics,
 
    wlan_intf_opcode_rssi,
 
    /// wlan_intf_opcode_msm_end -> 0x1fffffff
    wlan_intf_opcode_msm_end = 536870911,
 
    /// wlan_intf_opcode_security_start -> 0x20010000
    wlan_intf_opcode_security_start = 536936448,
 
    /// wlan_intf_opcode_security_end -> 0x2fffffff
    wlan_intf_opcode_security_end = 805306367,
 
    /// wlan_intf_opcode_ihv_start -> 0x30000000
    wlan_intf_opcode_ihv_start = 805306368,
 
    /// wlan_intf_opcode_ihv_end -> 0x3fffffff
    wlan_intf_opcode_ihv_end = 1073741823,
}
 
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
public struct WlanPhyRadioState
{
    public int dwPhyIndex;
    public Dot11RadioState dot11SoftwareRadioState;
    public Dot11RadioState dot11HardwareRadioState;
}
 
public enum Dot11RadioState : uint
{
    Unknown = 0,
    On,
    Off
}
 
public enum WLAN_OPCODE_VALUE_TYPE
{
    /// wlan_opcode_value_type_query_only -> 0
    wlan_opcode_value_type_query_only = 0,
 
    /// wlan_opcode_value_type_set_by_group_policy -> 1
    wlan_opcode_value_type_set_by_group_policy = 1,
 
    /// wlan_opcode_value_type_set_by_user -> 2
    wlan_opcode_value_type_set_by_user = 2,
 
    /// wlan_opcode_value_type_invalid -> 3
    wlan_opcode_value_type_invalid = 3
}
 
[DllImport("Wlanapi", EntryPoint = "WlanQueryInterface")]
public static extern uint WlanQueryInterface(
    [In] IntPtr hClientHandle,
    [In] ref Guid pInterfaceGuid,
    WLAN_INTF_OPCODE OpCode,
    IntPtr pReserved,
    [Out] out uint pdwDataSize,
    ref IntPtr ppData,
    IntPtr pWlanOpcodeValueType
);
 
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
public struct WLAN_CONNECTION_ATTRIBUTES
{
    /// WLAN_INTERFACE_STATE->_WLAN_INTERFACE_STATE
    public WLAN_INTERFACE_STATE isState;
 
    /// WLAN_CONNECTION_MODE->_WLAN_CONNECTION_MODE
    public WLAN_CONNECTION_MODE wlanConnectionMode;
 
    /// WCHAR[256]
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 256)]
    public string strProfileName;
 
    /// WLAN_ASSOCIATION_ATTRIBUTES->_WLAN_ASSOCIATION_ATTRIBUTES
    public WLAN_ASSOCIATION_ATTRIBUTES wlanAssociationAttributes;
 
    /// WLAN_SECURITY_ATTRIBUTES->_WLAN_SECURITY_ATTRIBUTES
    public WLAN_SECURITY_ATTRIBUTES wlanSecurityAttributes;
}
 
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct DOT11_MAC_ADDRESS
{
     public byte one;
     public byte two;
     public byte three;
     public byte four;
     public byte five;
     public byte six;
}
 
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
public struct WLAN_ASSOCIATION_ATTRIBUTES
{
    /// DOT11_SSID->_DOT11_SSID
    public DOT11_SSID dot11Ssid;
 
    /// DOT11_BSS_TYPE->_DOT11_BSS_TYPE
    public DOT11_BSS_TYPE dot11BssType;
 
    /// DOT11_MAC_ADDRESS->UCHAR[6]
    //// public DOT11_MAC_ADDRESS dot11Bssid;
    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 6)]
    public byte[] _dot11Bssid;
 
    /// DOT11_PHY_TYPE->_DOT11_PHY_TYPE
    public DOT11_PHY_TYPE dot11PhyType;
 
    /// ULONG->unsigned int
    public uint uDot11PhyIndex;
 
    /// WLAN_SIGNAL_QUALITY->ULONG->unsigned int
    public uint wlanSignalQuality;
 
    /// ULONG->unsigned int
    public uint ulRxRate;
 
    /// ULONG->unsigned int
    public uint ulTxRate;
}
 
[StructLayout(LayoutKind.Sequential)]
public struct WLAN_SECURITY_ATTRIBUTES
{
    /// <summary>
    /// BOOL->int
    /// </summary>
    [MarshalAs(UnmanagedType.Bool)]
    public bool bSecurityEnabled;
 
    /// <summary>
    /// BOOL->int
    /// </summary>
    [MarshalAs(UnmanagedType.Bool)]
    public bool bOneXEnabled;
 
    /// <summary>
    /// DOT11_AUTH_ALGORITHM->_DOT11_AUTH_ALGORITHM
    /// </summary>
    public DOT11_AUTH_ALGORITHM dot11AuthAlgorithm;
 
    /// <summary>
    /// DOT11_CIPHER_ALGORITHM->_DOT11_CIPHER_ALGORITHM
    /// </summary>
    public DOT11_CIPHER_ALGORITHM dot11CipherAlgorithm;
}
 
[DllImport("wlanapi.dll")]
public static extern int WlanGetNetworkBssList(
    [In] IntPtr hClientHandle,
    [In, MarshalAs(UnmanagedType.LPStruct)] Guid interfaceGuid,
    [In] IntPtr dot11SsidInt,
    [In] DOT11_BSS_TYPE dot11BssType,
    [In] bool securityEnabled,
    IntPtr reservedPtr,
    [Out] out IntPtr wlanBssList
);
 
[StructLayout(LayoutKind.Sequential)]
public struct WLAN_BSS_ENTRY
{
    /// <summary>
    /// Contains the SSID of the access point (AP) associated with the BSS.
    /// </summary>
    public DOT11_SSID dot11Ssid;
    /// <summary>
    /// The identifier of the PHY on which the AP is operating.
    /// </summary>
    public uint phyId;
    /// <summary>
    /// Contains the BSS identifier.
    /// </summary>
    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 6)]
    public byte[] dot11Bssid;
    /// <summary>
    /// Specifies whether the network is infrastructure or ad hoc.
    /// </summary>
    public DOT11_BSS_TYPE dot11BssType;
    public DOT11_PHY_TYPE dot11BssPhyType;
    /// <summary>
    /// The received signal strength in dBm.
    /// </summary>
    public int rssi;
    /// <summary>
    /// The link quality reported by the driver. Ranges from 0-100.
    /// </summary>
    public uint linkQuality;
    /// <summary>
    /// If 802.11d is not implemented, the network interface card (NIC) must set this field to TRUE. If 802.11d is implemented (but not necessarily enabled), the NIC must set this field to TRUE if the BSS operation complies with the configured regulatory domain.
    /// </summary>
    public bool inRegDomain;
    /// <summary>
    /// Contains the beacon interval value from the beacon packet or probe response.
    /// </summary>
    public ushort beaconPeriod;
    /// <summary>
    /// The timestamp from the beacon packet or probe response.
    /// </summary>
    public ulong timestamp;
    /// <summary>
    /// The host timestamp value when the beacon or probe response is received.
    /// </summary>
    public ulong hostTimestamp;
    /// <summary>
    /// The capability value from the beacon packet or probe response.
    /// </summary>
    public ushort capabilityInformation;
    /// <summary>
    /// The frequency of the center channel, in kHz.
    /// </summary>
    public uint chCenterFrequency;
    /// <summary>
    /// Contains the set of data transfer rates supported by the BSS.
    /// </summary>
    public WLAN_RATE_SET wlanRateSet;
    /// <summary>
    /// Offset of the information element (IE) data blob.
    /// </summary>
    public uint ieOffset;
    /// <summary>
    /// Size of the IE data blob, in bytes.
    /// </summary>
    public uint ieSize;
}
 
[StructLayout(LayoutKind.Sequential)]
public struct WLAN_RATE_SET
{
    /// <summary>
    /// The length, in bytes, of <see cref="rateSet"/>.
    /// </summary>
    private uint rateSetLength;
    /// <summary>
    /// An array of supported data transfer rates.
    /// If the rate is a basic rate, the first bit of the rate value is set to 1.
    /// A basic rate is the data transfer rate that all stations in a basic service set (BSS) can use to receive frames from the wireless medium.
    /// </summary>
    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 126)]
    private ushort[] rateSet;
 
    public ushort[] Rates
    {
        get
        {
            ushort[] rates = new ushort[rateSetLength / sizeof(ushort)];
            Array.Copy(rateSet, rates, rates.Length);
            return rates;
        }
    }
 
    /// <summary>
    /// CalculateS the data transfer rate in Mbps for an arbitrary supported rate.
    /// </summary>
    /// <param name="rate"></param>
    /// <returns></returns>
    public double GetRateInMbps(int rate)
    {
        return (rateSet[rate] & 0x7FFF) * 0.5;
    }
}
 
[StructLayout(LayoutKind.Sequential)]
internal struct WlanBssListHeader
{
    internal uint totalSize;
    internal uint numberOfItems;
}
 
public struct WLAN_BSS_LIST
{
    public uint dwTotalSize;
    public uint dwNumberOfItems;
    public WLAN_BSS_ENTRY[] wlanBssEntries;
 
    public WLAN_BSS_LIST(IntPtr ppWlanBssList)
    {
        var uintSize = Marshal.SizeOf(typeof(uint)); // 4
 
        dwTotalSize = (uint)Marshal.ReadInt32(ppWlanBssList, 0);
        dwNumberOfItems = (uint)Marshal.ReadInt32(ppWlanBssList, uintSize /* Offset for dwTotalSize */);
        wlanBssEntries = new WLAN_BSS_ENTRY[dwNumberOfItems];
 
        for (int i = 0; i < dwNumberOfItems; i++)
        {
            var wlanBssEntry = new IntPtr(ppWlanBssList.ToInt64()
                + (uintSize * 2) /* Offset for dwTotalSize and dwNumberOfItems */
                + (Marshal.SizeOf(typeof(WLAN_BSS_ENTRY)) * i) /* Offset for preceding items */);
 
            wlanBssEntries[i] = Marshal.PtrToStructure<WLAN_BSS_ENTRY>(wlanBssEntry);
        }
    }
}
'@


Add-Type -MemberDefinition $WlanGetProfileListSig -Name ProfileManagement -Namespace WiFi -Using System.Text -PassThru