Tutorial: Deploy Always On VPN - Configure Always On VPN profile for Windows 10+ clients

Applies to: Windows Server 2022, Windows Server 2019, Windows Server 2016, Windows Server 2012 R2, Windows 10, Windows 11

In this last part of the tutorial, you'll learn how to use a ProfileXML PowerShell configuration script to configure Always On VPN settings and create a user tunnel for client connections.

For more detailed information on Always on VPN configuration options for the configuration service provider (CSP), see VPNv2 configuration service provider.


Complete Tutorial: Deploy Always On VPN - Configure Certificate Authority templates.

Create an Always On VPN client profile

In this section, we'll create a VPN client connection in order to verify that the VPN test client can establish a successful VPN connection. This will also allow us to create the EAP settings for export in the next section.

For more information about EAP settings, see EAP configuration.

  1. Sign in to the domain-joined VPN client computer as the VPN user you created in Create Active Directory test user.

  2. On the Start menu, type VPN to select VPN Settings. Press ENTER.

  3. In the details pane, select Add a VPN connection.

  4. For VPN Provider, select Windows (built-in).

  5. For Connection Name, enter Contoso VPN.

  6. For Server name or address, enter the external FQDN of your VPN server (for example, vpn.contoso.com).

  7. For VPN type, select IKEv2.

  8. For Type of sign-in info, select Certificate.

  9. Select Save.

  10. Under Related Settings, select Change adapter options.

  11. Right-click Contoso VPN, and select Properties.

  12. On the Security tab, for Data encryption, select Maximum strength encryption.

  13. Select Use Extensible Authentication Protocol (EAP). Then, for Use Extensible Authentication Protocol (EAP), select Microsoft: Protected EAP (PEAP) (encryption enabled).

  14. Select Properties to open Protected EAP Properties, and complete the following steps:

    1. For Connect to these servers, enter the name of the NPS server.

    2. For Trusted Root Certification Authorities, select the CA that issued the NPS server's certificate (for example, contoso-CA).

    3. For Notifications before connecting, select Don't ask user to authorize new servers or trusted CAs.

    4. For Select Authentication Method, select Smart Card or other certificate.

    5. Select Configure.

      1. Select Use a certificate on this computer.

      2. For Connect to these servers, enter the name of the NPS server.

      3. For Trusted Root Certification Authorities, select the CA that issued the NPS server's certificate.

      4. Select Don't prompt user to authorize new servers or trusted certification authorities.

      5. Select OK to close Smart Card or other Certificate Properties.

      6. Select OK to close Protected EAP Properties.

    6. Select OK to close Contoso VPN Properties.

  15. Close the Network Connections window.

  16. In Settings, select Contoso VPN, and then select Connect.


Make sure that the template VPN connection to your VPN server is successful. Doing so ensures that the EAP settings are correct before you use them in the next step. You must connect at least once before continuing; otherwise, the profile will not contain all the information necessary to connect to the VPN.

Configure your Windows VPN client

In this section, you'll manually configure the Windows VPN client using a PowerShell script.

  1. Sign in as your VPN User to the VPN client computer.

  2. Open Windows PowerShell integrated scripting environment (ISE) as Administrator.

  3. Copy and paste the following script:

    # Define key VPN profile parameters
    # Replace with your own values
    $Domain = 'corp' # Name of the domain.
    $TemplateName = 'Contoso VPN' # Name of the test VPN connection you created in the tutorial. 
    $ProfileName = 'Contoso AlwaysOn VPN' # Name of the profile we are going to create.
    $Servers = 'aov-vpn.contoso.com' #Public or routable IP address or DNS name for the VPN gateway.
    $DnsSuffix = 'corp.contoso.com' # Specifies one or more commas separated DNS suffixes. 
    $DomainName = '.corp.contoso.com' #Used to indicate the namespace to which the policy applies. Contains `.` prefix.
    $DNSServers = '' #List of comma-separated DNS Server IP addresses to use for the namespace.
    $TrustedNetwork = 'corp.contoso.com' #Comma-separated string to identify the trusted network.
    #Get the EAP settings for the current profile called $TemplateName
    $Connection = Get-VpnConnection -Name $TemplateName
        $Message = "Unable to get $TemplateName connection profile: $_"
        Write-Host "$Message"
    $EAPSettings= $Connection.EapConfigXmlStream.InnerXml
    $ProfileNameEscaped = $ProfileName -replace ' ', '%20'
    # Define ProfileXML
    $ProfileXML = @("
    #Output the XML for possible use in Intune
    $ProfileXML | Out-File -FilePath ($env:USERPROFILE + '\desktop\VPN_Profile.xml')
    # Escape special characters in the profile (<,>,")
    $ProfileXML = $ProfileXML -replace '<', '&lt;'
    $ProfileXML = $ProfileXML -replace '>', '&gt;'
    $ProfileXML = $ProfileXML -replace '"', '&quot;'
    # Define WMI-to-CSP Bridge properties
    $nodeCSPURI = "./Vendor/MSFT/VPNv2"
    $namespaceName = "root\cimv2\mdm\dmmap"
    $className = "MDM_VPNv2_01"
        # Determine user SID for VPN profile.
        $WmiLoggedOnUsers = (Get-WmiObject Win32_LoggedOnUser).Antecedent
        If($WmiLoggedOnUsers.Count -gt 1) { 
            $WmiLoggedOnUsers = $WmiLoggedOnUsers -match "Domain=""$Domain"""
        $WmiUserValid = ($WmiLoggedOnUsers | Select-Object -Unique -First 1) -match 'Domain="([^"]+)",Name="([^"]+)"'
        If(-not $WmiUserValid){
            Throw "Returned object is not a valid WMI string"
        $UserName = "$($Matches[1])\$($Matches[2])"
        $ObjUser = New-Object System.Security.Principal.NTAccount($UserName)
        $Sid = $ObjUser.Translate([System.Security.Principal.SecurityIdentifier])
        $SidValue = $Sid.Value
        $Message = "User SID is $SidValue."
        Write-Host "$Message"
    catch [Exception] 
        $Message = "Unable to get user SID. $_"
        Write-Host "$Message" 
        # Define WMI session.
        $session = New-CimSession
        $options = New-Object Microsoft.Management.Infrastructure.Options.CimOperationOptions
        $options.SetCustomOption("PolicyPlatformContext_PrincipalContext_Type", "PolicyPlatform_UserContext", $false)
        $options.SetCustomOption("PolicyPlatformContext_PrincipalContext_Id", "$SidValue", $false)
    catch {
        $Message = "Unable to create new session for $ProfileName profile: $_"
        Write-Host $Message
        #Detect and delete previous VPN profile.
        $deleteInstances = $session.EnumerateInstances($namespaceName, $className, $options)
        foreach ($deleteInstance in $deleteInstances)
            $InstanceId = $deleteInstance.InstanceID
            if ("$InstanceId" -eq "$ProfileNameEscaped")
                $session.DeleteInstance($namespaceName, $deleteInstance, $options)
                $Message = "Removed $ProfileName profile $InstanceId" 
                Write-Host "$Message"
                $Message = "Ignoring existing VPN profile $InstanceId"
                Write-Host "$Message"
    catch [Exception]
        $Message = "Unable to remove existing outdated instance(s) of $ProfileName profile: $_"
        Write-Host $Message
        # Create the VPN profile.
        $newInstance = New-Object Microsoft.Management.Infrastructure.CimInstance $className, $namespaceName
        $property = [Microsoft.Management.Infrastructure.CimProperty]::Create("ParentID", "$nodeCSPURI", "String", "Key")
        $property = [Microsoft.Management.Infrastructure.CimProperty]::Create("InstanceID", "$ProfileNameEscaped", "String", "Key")
        $property = [Microsoft.Management.Infrastructure.CimProperty]::Create("ProfileXML", "$ProfileXML", "String", "Property")
        $session.CreateInstance($namespaceName, $newInstance, $options)
        $Message = "Created $ProfileName profile."
        Write-Host "$Message"
    catch [Exception]
        $Message = "Unable to create $ProfileName profile: $_"
        Write-Host "$Message"
    $Message = "Script Complete"
    Write-Host "$Message"
  4. Set the value for the following variables at the top of the script: $Domain, $TemplateName, $ProfileName, $Servers, $DnsSuffix, $DomainName, and $DNSServers. For more detailed information about how to set these variables, see: VPNv2 CSP.

  5. Press ENTER to run the script.

  6. Verify that the script was successful by running the following command in the Windows PowerShell ISE:

        Get-CimInstance -Namespace root\cimv2\mdm\dmmap -ClassName MDM_VPNv2_01
  7. You should see the following output (the ProfileXML value has been truncated for readability):

    AlwaysOn                : True
    ByPassForLocal          : 
    DeviceTunnel            : 
    DnsSuffix               : corp.contoso.com
    EdpModeId               : 
    InstanceID              : Contoso%20AlwaysOn%20VPN
    LockDown                : 
    ParentID                : ./Vendor/MSFT/VPNv2
    ProfileXML              : <VPNProfile>...</VPNProfile>
    RegisterDNS             : 
    RememberCredentials     : True
    TrustedNetworkDetection : corp.contoso.com
    PSComputerName          : 

You have now configured your user tunnel for Always On VPN. If you want to learn how to configure a device tunnel, see Configure VPN device tunnels in Windows client.

Next steps