Runtime/Invoke-OSDStageWiFiProfiles.ps1

[CmdletBinding()]
param([string]$ConfigPath)
$ErrorActionPreference='Stop'
if([string]::IsNullOrWhiteSpace($ConfigPath)){$ConfigPath=Join-Path $PSScriptRoot 'config.json'}

function Initialize-OSDStageWiFiNative {
    if('OSDStage.WiFiNative' -as [type]){return}
    Add-Type -TypeDefinition @'
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
namespace OSDStage {
    public sealed class WiFiImportResult {
        public string InterfaceId;
        public uint ErrorCode;
        public uint ReasonCode;
    }
    public static class WiFiNative {
        [StructLayout(LayoutKind.Sequential, CharSet=CharSet.Unicode)]
        private struct InterfaceInfo {
            public Guid Id;
            [MarshalAs(UnmanagedType.ByValTStr, SizeConst=256)] public string Description;
            public int State;
        }
        [DllImport("wlanapi.dll")] private static extern uint WlanOpenHandle(uint version, IntPtr reserved, out uint negotiated, out IntPtr handle);
        [DllImport("wlanapi.dll")] private static extern uint WlanCloseHandle(IntPtr handle, IntPtr reserved);
        [DllImport("wlanapi.dll")] private static extern uint WlanEnumInterfaces(IntPtr handle, IntPtr reserved, out IntPtr list);
        [DllImport("wlanapi.dll")] private static extern void WlanFreeMemory(IntPtr memory);
        [DllImport("wlanapi.dll", CharSet=CharSet.Unicode)]
        private static extern uint WlanSetProfile(IntPtr handle, ref Guid id, uint flags, string xml, string security,
            [MarshalAs(UnmanagedType.Bool)] bool overwrite, IntPtr reserved, out uint reason);
        public static WiFiImportResult[] Import(string xml) {
            IntPtr handle=IntPtr.Zero, list=IntPtr.Zero;
            try {
                uint negotiated;
                uint code=WlanOpenHandle(2,IntPtr.Zero,out negotiated,out handle);
                if(code!=0) throw new InvalidOperationException("WlanOpenHandle error " + code);
                code=WlanEnumInterfaces(handle,IntPtr.Zero,out list);
                if(code!=0) throw new InvalidOperationException("WlanEnumInterfaces error " + code);
                int count=Marshal.ReadInt32(list);
                if(count==0) throw new InvalidOperationException("No enabled Wi-Fi interface is available.");
                int size=Marshal.SizeOf(typeof(InterfaceInfo));
                var results=new List<WiFiImportResult>();
                for(int i=0;i<count;i++) {
                    var info=(InterfaceInfo)Marshal.PtrToStructure(IntPtr.Add(list,8+i*size),typeof(InterfaceInfo));
                    uint reason;
                    // flags=0 creates an all-user profile. A same-name profile is updated.
                    code=WlanSetProfile(handle,ref info.Id,0,xml,null,true,IntPtr.Zero,out reason);
                    results.Add(new WiFiImportResult {InterfaceId=info.Id.ToString(),ErrorCode=code,ReasonCode=reason});
                }
                return results.ToArray();
            }
            finally {
                if(list!=IntPtr.Zero)WlanFreeMemory(list);
                if(handle!=IntPtr.Zero)WlanCloseHandle(handle,IntPtr.Zero);
            }
        }
    }
}
'@
 -ErrorAction Stop
}

function Import-OSDStageWiFiNativeProfile {
    param([Parameter(Mandatory)][string]$Xml)
    [OSDStage.WiFiNative]::Import($Xml)
}

function Invoke-OSDStageWiFiProfileOperation {
    param([Parameter(Mandatory)]$Operation)
    # Never include XML or native exception messages in reports: they may contain credentials.
    try {
        $interfaces=@(Import-OSDStageWiFiNativeProfile -Xml ([string]$Operation.Xml))
        $failed=@($interfaces | Where-Object ErrorCode -ne 0).Count
        if($interfaces.Count -eq 0){throw 'No interfaces.'}
        [pscustomobject]@{
            Name=[string]$Operation.Name
            Status=if($failed){'Failed'}else{'Succeeded'}
            Outcome=if($failed){'ImportFailed'}else{'Imported'}
            Interfaces=$interfaces
            Error=if($failed){'Wi-Fi profile import failed; see numeric interface error and reason codes.'}else{$null}
        }
    }
    catch {
        [pscustomobject]@{Name=[string]$Operation.Name;Status='Failed';Outcome='ImportFailed';Interfaces=@();Error='Wi-Fi import could not run. Check WLAN service, driver and adapter availability.'}
    }
}

$configuration=$null
$exitCode=1
$items=@()
$errorMessage=$null
try {
    $configuration=Get-Content -LiteralPath $ConfigPath -Raw -Encoding UTF8 | ConvertFrom-Json -ErrorAction Stop
    $id=[guid]::Empty
    if([int]$configuration.SchemaVersion -ne 2 -or -not [guid]::TryParse([string]$configuration.DeploymentId,[ref]$id) -or
        [string]::IsNullOrWhiteSpace([string]$configuration.ResultPath) -or @($configuration.Operations).Count -eq 0) {throw 'Invalid configuration.'}
    foreach($operation in @($configuration.Operations)) {
        if([string]::IsNullOrWhiteSpace([string]$operation.Name) -or [string]::IsNullOrWhiteSpace([string]$operation.Xml)){throw 'Invalid operation.'}
    }
    $service=Get-Service -Name WlanSvc -ErrorAction Stop
    if($service.Status -ne 'Running'){Start-Service -Name WlanSvc -ErrorAction Stop}
    Initialize-OSDStageWiFiNative
    $items=@(foreach($operation in @($configuration.Operations)){Invoke-OSDStageWiFiProfileOperation -Operation $operation})
    if(@($items | Where-Object Status -ne 'Succeeded').Count -eq 0){$exitCode=0}
    else{$errorMessage='One or more Wi-Fi profile imports failed.'}
}
catch {$errorMessage='Wi-Fi staging could not be processed. Check configuration, WLAN service and driver availability.'}
finally {
    # Credential-bearing staging is consumed once, including on failure and CleanupMode Never.
    try {Remove-Item -LiteralPath $ConfigPath -Force -ErrorAction Stop}
    catch {$exitCode=1;$errorMessage='Wi-Fi configuration cleanup failed; the restricted staging file may still contain a shared key.'}
}
try {
    if($configuration -and $configuration.ResultPath) {
        $resultPath=[Environment]::ExpandEnvironmentVariables([string]$configuration.ResultPath)
        New-Item -ItemType Directory -Path (Split-Path $resultPath -Parent) -Force | Out-Null
        [ordered]@{
            SchemaVersion=2;DeploymentId=[string]$configuration.DeploymentId
            Status=if($exitCode -eq 0){'Succeeded'}else{'Failed'}
            OperationCount=@($configuration.Operations).Count
            SuccessfulOperationCount=@($items | Where-Object Status -eq 'Succeeded').Count
            FailedOperationCount=if($items.Count){@($items | Where-Object Status -ne 'Succeeded').Count}else{@($configuration.Operations).Count}
            RestartNeeded=$false;Items=@($items);Error=$errorMessage
        } | ConvertTo-Json -Depth 10 | Set-Content -LiteralPath $resultPath -Encoding UTF8 -ErrorAction Stop
    }
}
catch {$exitCode=1}
if($exitCode -ne 0){Write-Output 'Wi-Fi profile staging failed. See WiFiProfiles-Result.json when available.'}
exit $exitCode