Intune/Get-IntuneAlwaysOnVpnConfig.ps1

<#
.SYNOPSIS
    Evaluates whether Intune device configuration enforces always-on full-tunnel VPN
    on managed Windows devices and that the policy is actively assigned.
.DESCRIPTION
    Queries Intune device configuration profiles for Windows 10/11 VPN and checks
    whether always-on VPN is enabled (alwaysOn: true) and split tunneling is disabled
    (enableSplitTunneling: false), ensuring all traffic routes through the VPN at
    all times. Verifies at least one active assignment. Satisfies CMMC AC.L2-3.1.14.

    Requires an active Microsoft Graph connection with
    DeviceManagementConfiguration.Read.All permission.
.PARAMETER OutputPath
    Optional path to export results as CSV. If not specified, results are returned
    to the pipeline.
.EXAMPLE
    PS> .\Intune\Get-IntuneAlwaysOnVpnConfig.ps1

    Displays always-on VPN configuration evaluation results.
.EXAMPLE
    PS> .\Intune\Get-IntuneAlwaysOnVpnConfig.ps1 -OutputPath '.\intune-alwaysonvpn.csv'

    Exports the evaluation to CSV.
.NOTES
    Author: Daren9m
    CMMC: AC.L2-3.1.14 — Route remote access via managed access control points
#>

[CmdletBinding()]
param(
    [Parameter()]
    [ValidateNotNullOrEmpty()]
    [string]$OutputPath
)

$ErrorActionPreference = 'Stop'

$_scriptDir = if ($MyInvocation.MyCommand.Path) { Split-Path -Parent $MyInvocation.MyCommand.Path } else { $PSScriptRoot }
. (Join-Path -Path $_scriptDir -ChildPath '..\Common\SecurityConfigHelper.ps1')

$ctx = Initialize-SecurityConfig
$settings = $ctx.Settings
$checkIdCounter = $ctx.CheckIdCounter

function Add-Setting {
    param(
        [string]$Category, [string]$Setting, [string]$CurrentValue,
        [string]$RecommendedValue, [string]$Status,
        [string]$CheckId = '', [string]$Remediation = ''
    )
    $p = @{
        Settings         = $settings
        CheckIdCounter   = $checkIdCounter
        Category         = $Category
        Setting          = $Setting
        CurrentValue     = $CurrentValue
        RecommendedValue = $RecommendedValue
        Status           = $Status
        CheckId          = $CheckId
        Remediation      = $Remediation
    }
    Add-SecuritySetting @p
}

# ------------------------------------------------------------------
# 1. Check VPN device configuration profiles for always-on full tunnel
# with active assignments
# ------------------------------------------------------------------
try {
    Write-Verbose 'Checking Intune VPN configurations for always-on full-tunnel VPN...'
    $graphParams = @{
        Method      = 'GET'
        Uri         = '/beta/deviceManagement/deviceConfigurations?$expand=assignments'
        ErrorAction = 'Stop'
    }
    $configs = Invoke-MgGraphRequest @graphParams

    $configList = @()
    if ($configs -and $configs['value']) {
        $configList = @($configs['value'])
    }

    $compliantProfile = $null

    foreach ($config in $configList) {
        if ($config['@odata.type'] -notmatch 'windows10VpnConfiguration') { continue }
        if ($config['alwaysOn'] -ne $true) { continue }
        if ($config['enableSplitTunneling'] -ne $false) { continue }

        $assignments = @()
        if ($config['assignments']) { $assignments = @($config['assignments']) }

        if ($assignments.Count -gt 0) {
            $compliantProfile = $config
            break
        }
    }

    if ($compliantProfile) {
        $profileName  = $compliantProfile['displayName']
        $assignCount  = @($compliantProfile['assignments']).Count
        $currentValue = "Always-on full-tunnel VPN configured (Policy: $profileName, $assignCount assignment(s))"
        $status       = 'Pass'
    }
    else {
        $hasUnassigned = $configList | Where-Object {
            $_['@odata.type'] -match 'windows10VpnConfiguration' -and
            $_['alwaysOn'] -eq $true -and
            $_['enableSplitTunneling'] -eq $false
        }
        $currentValue = if ($hasUnassigned) {
            'Always-on full-tunnel VPN profile exists but has no active assignments'
        } else {
            'No windows10VpnConfiguration profile with alwaysOn: true and split tunneling disabled found'
        }
        $status = 'Fail'
    }

    $settingParams = @{
        Category         = 'Always-On VPN'
        Setting          = 'Always-On VPN with Full Tunnel (Assigned)'
        CurrentValue     = $currentValue
        RecommendedValue = 'windows10VpnConfiguration with alwaysOn: true and enableSplitTunneling: false assigned to at least one group'
        Status           = $status
        CheckId          = 'INTUNE-REMOTEVPN-001'
        Remediation      = 'Intune admin center > Devices > Configuration > Create profile > Windows 10 and later > VPN > set Always-on VPN to enable and split tunneling to disable. Assign the profile to device or user groups.'
    }
    Add-Setting @settingParams
}
catch {
    if ($_.Exception.Message -match '403|Forbidden|Authorization') {
        $settingParams = @{
            Category         = 'Always-On VPN'
            Setting          = 'Always-On VPN with Full Tunnel (Assigned)'
            CurrentValue     = 'Insufficient permissions or license (Intune required)'
            RecommendedValue = 'windows10VpnConfiguration with alwaysOn: true and enableSplitTunneling: false assigned to at least one group'
            Status           = 'Review'
            CheckId          = 'INTUNE-REMOTEVPN-001'
            Remediation      = 'Requires DeviceManagementConfiguration.Read.All permission and Intune license.'
        }
        Add-Setting @settingParams
    }
    else {
        Write-Warning "Could not check always-on VPN configuration: $_"
    }
}

# ------------------------------------------------------------------
# Output results
# ------------------------------------------------------------------
Export-SecurityConfigReport -Settings $settings -OutputPath $OutputPath -ServiceLabel 'Intune Always-On VPN'