Private/Get-WlanProfileList.ps1

<#
.SYNOPSIS
    Locale-invariant WLAN profile list helper (private).
.DESCRIPTION
    Get-WlanProfileList enumerates Wi-Fi profiles via wlanapi.dll. Profiles and
    interface descriptions come straight from the API, so no locale parsing is
    involved. Returns an empty array (NOT an error) when: not Windows,
    wlanapi.dll missing, WlanSvc stopped (WlanOpenHandle error 1062 or any
    nonzero), or zero interfaces.
.NOTES
    P/Invoke signatures verbatim from ManagedWifi (Apache-2.0). All enumeration
    and marshaling happens inside C# to avoid the PS 5.1 binder picking wrong
    PtrToStructure overloads; every list buffer is freed with WlanFreeMemory.
#>


function Get-WlanProfileList {
    [CmdletBinding()]
    [OutputType([PSCustomObject[]])]
    param()

    $result = @()

    # Cheap pre-check so macOS/Linux dev boxes degrade silently
    $isWindowsHost = $false
    try {
        $isWindowsHost = [System.Runtime.InteropServices.RuntimeInformation]::IsOSPlatform(
            [System.Runtime.InteropServices.OSPlatform]::Windows)
    }
    catch {
        # RuntimeInformation missing on old .NET Framework (PS 5.1)
        $isWindowsHost = ($env:OS -eq 'Windows_NT')
    }
    if (-not $isWindowsHost) {
        Write-Verbose 'Get-WlanProfileList: not a Windows host; WLAN not applicable'
        return $result
    }

    if (-not ([bool]('WlanNative' -as [type]))) {
        $wlanNativeSource = @'
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
public static class WlanNative {
    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
    public struct WLAN_INTERFACE_INFO {
        public Guid InterfaceGuid;
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 256)]
        public string InterfaceDescription;
        public int isState;
    }
    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
    public struct WLAN_PROFILE_INFO {
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 256)]
        public string strProfileName;
        public uint dwFlags;
    }
    [DllImport("wlanapi.dll")]
    public static extern int WlanOpenHandle(uint clientVersion, IntPtr pReserved, out uint negotiatedVersion, out IntPtr clientHandle);
    [DllImport("wlanapi.dll")]
    public static extern int WlanEnumInterfaces(IntPtr clientHandle, IntPtr pReserved, out IntPtr ppInterfaceList);
    [DllImport("wlanapi.dll")]
    public static extern int WlanGetProfileList(IntPtr clientHandle, [MarshalAs(UnmanagedType.LPStruct)] Guid interfaceGuid, IntPtr pReserved, out IntPtr ppProfileList);
    [DllImport("wlanapi.dll")]
    public static extern void WlanFreeMemory(IntPtr pMemory);
    [DllImport("wlanapi.dll")]
    public static extern int WlanCloseHandle(IntPtr clientHandle, IntPtr pReserved);
    public class WlanProfile {
        public string Name;
        public uint Flags;
        public string InterfaceDescription;
    }
    public static WlanProfile[] GetProfiles(out int win32Error) {
        win32Error = 0;
        List<WlanProfile> profiles = new List<WlanProfile>();
        IntPtr clientHandle = IntPtr.Zero;
        uint negotiatedVersion = 0;
        int err = WlanOpenHandle(2, IntPtr.Zero, out negotiatedVersion, out clientHandle);
        if (err != 0) { win32Error = err; return null; }
        try {
            IntPtr pInterfaceList;
            err = WlanEnumInterfaces(clientHandle, IntPtr.Zero, out pInterfaceList);
            if (err != 0) { win32Error = err; return null; }
            try {
                // List header is { uint dwNumberOfItems; uint dwIndex; } then the item array
                int interfaceCount = Marshal.ReadInt32(pInterfaceList);
                int interfaceSize = Marshal.SizeOf(typeof(WLAN_INTERFACE_INFO));
                IntPtr currentInterface = new IntPtr(pInterfaceList.ToInt64() + 8);
                for (int i = 0; i < interfaceCount; i++) {
                    WLAN_INTERFACE_INFO iface = (WLAN_INTERFACE_INFO)Marshal.PtrToStructure(currentInterface, typeof(WLAN_INTERFACE_INFO));
                    currentInterface = new IntPtr(currentInterface.ToInt64() + interfaceSize);
                    IntPtr pProfileList;
                    int profileErr = WlanGetProfileList(clientHandle, iface.InterfaceGuid, IntPtr.Zero, out pProfileList);
                    if (profileErr != 0) { continue; }
                    try {
                        int profileCount = Marshal.ReadInt32(pProfileList);
                        int profileSize = Marshal.SizeOf(typeof(WLAN_PROFILE_INFO));
                        IntPtr currentProfile = new IntPtr(pProfileList.ToInt64() + 8);
                        for (int j = 0; j < profileCount; j++) {
                            WLAN_PROFILE_INFO info = (WLAN_PROFILE_INFO)Marshal.PtrToStructure(currentProfile, typeof(WLAN_PROFILE_INFO));
                            currentProfile = new IntPtr(currentProfile.ToInt64() + profileSize);
                            WlanProfile profile = new WlanProfile();
                            profile.Name = info.strProfileName;
                            profile.Flags = info.dwFlags;
                            profile.InterfaceDescription = iface.InterfaceDescription;
                            profiles.Add(profile);
                        }
                    } finally { WlanFreeMemory(pProfileList); }
                }
            } finally { WlanFreeMemory(pInterfaceList); }
        } finally { WlanCloseHandle(clientHandle, IntPtr.Zero); }
        return profiles.ToArray();
    }
}
'@

        try {
            Add-Type -TypeDefinition $wlanNativeSource -ErrorAction Stop
        }
        catch {
            # Add-Type -TypeDefinition is blocked under Constrained Language Mode (JEA)
            Write-Verbose "Get-WlanProfileList: Add-Type WlanNative failed: $($_.Exception.Message)"
            return $result
        }
    }

    try {
        $win32Error = 0
        $profiles = [WlanNative]::GetProfiles([ref]$win32Error)
        if ($null -eq $profiles) {
            # 1062 = WlanSvc not running; any nonzero is 'not applicable', not an error
            Write-Verbose "Get-WlanProfileList: WLAN API unavailable (win32 $win32Error)"
            return $result
        }
        foreach ($wlanProfile in $profiles) {
            $flags = [uint32]$wlanProfile.Flags
            $result += [PSCustomObject]@{
                Name                 = $wlanProfile.Name
                Flags                = $flags
                IsGroupPolicy        = [bool]($flags -band 1)
                IsPerUser            = [bool]($flags -band 2)
                InterfaceDescription = $wlanProfile.InterfaceDescription
            }
        }
    }
    catch {
        # wlanapi.dll missing etc.: 'not applicable', not an error
        Write-Verbose "Get-WlanProfileList: enumeration failed: $($_.Exception.Message)"
        $result = @()
    }

    return $result
}