Website: (config builder) Add Wifi settings for Windows. (#31235)

Changes:
- Updated the configuration profile builder to support Windows settings
that have a templated setting target (e.g.,
`./Device/Vendor/MSFT/WiFi/Profile/{SSID}/WlanXml`)
- Updated the configuration profile builder to include Wi-fi settings
for Windows devices.
This commit is contained in:
Eric 2025-07-24 12:39:23 -05:00 committed by GitHub
parent 2ec413776e
commit fa0b595a10
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 206 additions and 6 deletions

View file

@ -2508,6 +2508,176 @@ parasails.registerPage('configuration-builder', {
},
],
},
{
subcategoryName: 'WiFi',
subcategorySlug: 'windows-wifi',
description: 'Settings related to wireless networks on Windows devices.',
learnMoreLinkUrl: 'https://learn.microsoft.com/en-us/windows/client-management/mdm/wifi-csp',
payloads: [
{
name: 'Add WiFi network (device scope)',
uniqueSlug: 'windows-wifi-add-network',
tooltip: 'This policy lets you add a Wi-Fi network on a windows device.',
category: 'Wi-Fi',
supportedAccessTypes: ['add', 'replace'],
formInput: {
type: 'multifield',
inputs: [
{
type: 'text',
name: 'Network SSID',
slug: 'ssid',
},
{
type: 'text',
name: 'Network password',
slug: 'password',
},
{
type: 'select',
label: 'Encryption type',
slug: 'networkEncryptionType',
options: [
// { // TODO: these values are hidden because they require a different keyType value. https://learn.microsoft.com/en-us/windows/win32/nativewifi/wlan-profileschema-sharedkey-security-element#keymaterial
// name: 'Open',
// value: 'open'
// },
// {
// name: 'Shared',
// value: 'shared',
// },
// {
// name: 'WPA Enterprise',
// value: 'WPA',
// },
{
name: 'WPA Personal',
value: 'WPAPSK',
},
// {
// name: 'WPA2 Enterprise',
// value: 'WPA2',
// },
{
name: 'WPA2 Personal',
value: 'WPA2PSK',
},
{
name: 'WPA3 Personal',
value: `WPA3SAE`,
},
// {
// name: 'WPA3 Enterprise 192-bit mode authentication.',
// value: 'WPA3ENT192',
// },
// {
// name: 'WPA3 Enterprise',
// value: 'WPA2PSK',
// },
]
},
{
type: 'booleanWithLabel',
label: 'Automatically connect to network',
slug: 'connectionMode',
},
]
},
formOutput: {
settingFormat: 'chr',
settingTargetTemplate: `./Device/Vendor/MSFT/WiFi/Profile/<%= ssid %>/WlanXml`,
outputTemplate: `<![CDATA[<?xml version="1.0"?><WLANProfile xmlns="http://www.microsoft.com/networking/WLAN/profile/v1"><name><%= ssid %></name><SSIDConfig><SSID><name><%= ssid %></name></SSID></SSIDConfig><connectionType>ESS</connectionType><connectionMode><%= connectionMode %></connectionMode><MSM><security><authEncryption><authentication><%= networkEncryptionType %></authentication><encryption>AES</encryption><useOneX>false</useOneX><transitionMode xmlns="http://www.microsoft.com/networking/WLAN/profile/v4">true</transitionMode></authEncryption><sharedKey><keyType>passPhrase</keyType><protected>false</protected><keyMaterial><%= password %></keyMaterial></sharedKey></security></MSM></WLANProfile>]]>`,
valuesToTransform: {
'connectionMode': {
true: 'auto',
false: 'manual',
},
}
},
},
{
name: 'Add WiFi network (user scope)',
uniqueSlug: 'windows-wifi-add-network-user-scope',
tooltip: 'This policy lets you add a Wi-Fi network on a windows device.',
category: 'Wi-Fi',
supportedAccessTypes: ['add', 'replace'],
formInput: {
type: 'multifield',
inputs: [
{
type: 'text',
name: 'Network SSID',
slug: 'ssid',
},
{
type: 'text',
name: 'Network password',
slug: 'password',
},
{
type: 'select',
label: 'Encryption type',
slug: 'networkEncryptionType',
options: [
// { // TODO: these values are hidden because they require a different keyType value. https://learn.microsoft.com/en-us/windows/win32/nativewifi/wlan-profileschema-sharedkey-security-element#keymaterial
// name: 'Open',
// value: 'open'
// },
// {
// name: 'Shared',
// value: 'shared',
// },
// {
// name: 'WPA Enterprise',
// value: 'WPA',
// },
{
name: 'WPA Personal',
value: 'WPAPSK',
},
// {
// name: 'WPA2 Enterprise',
// value: 'WPA2',
// },
{
name: 'WPA2 Personal',
value: 'WPA2PSK',
},
{
name: 'WPA3 Personal',
value: `WPA3SAE`,
},
// {
// name: 'WPA3 Enterprise 192-bit mode authentication.',
// value: 'WPA3ENT192',
// },
// {
// name: 'WPA3 Enterprise',
// value: 'WPA2PSK',
// },
]
},
{
type: 'booleanWithLabel',
label: 'Automatically connect to network',
slug: 'connectionMode',
},
]
},
formOutput: {
settingFormat: 'chr',
settingTargetTemplate: `./User/Vendor/MSFT/WiFi/Profile/<%= ssid %>/WlanXml`,
outputTemplate: `<![CDATA[<?xml version="1.0"?><WLANProfile xmlns="http://www.microsoft.com/networking/WLAN/profile/v1"><name><%= ssid %></name><SSIDConfig><SSID><name><%= ssid %></name></SSID></SSIDConfig><connectionType>ESS</connectionType><connectionMode><%= connectionMode %></connectionMode><MSM><security><authEncryption><authentication><%= networkEncryptionType %></authentication><encryption>AES</encryption><useOneX>false</useOneX><transitionMode xmlns="http://www.microsoft.com/networking/WLAN/profile/v4">true</transitionMode></authEncryption><sharedKey><keyType>passPhrase</keyType><protected>false</protected><keyMaterial><%= password %></keyMaterial></sharedKey></security></MSM></WLANProfile>]]>`,
valuesToTransform: {
'connectionMode': {
true: 'auto',
false: 'manual',
},
}
},
},
],
},
],
}
],
@ -2578,6 +2748,23 @@ parasails.registerPage('configuration-builder', {
// Get the selected access type for this payload
let accessType = this.configurationBuilderFormData[payload.uniqueSlug+'-access-type'];
let value;
// If a payload uses a settingTargetTemplate, build the settting target value and add it to the payload.
if(payloadToAdd.formOutput.settingTargetTemplate) {
let templateToUseForSettingTarget = _.template(payloadToAdd.formOutput.settingTargetTemplate);
let formDataForThisPayloadToBuildTheSettingTarget = {};
if(payload.formInput.type === 'multifield') {
for(let input of payload.formInput.inputs) {
if(payload.formOutput.valuesToTransform && payload.formOutput.valuesToTransform[input.slug]){
formDataForThisPayloadToBuildTheSettingTarget[input.slug] = encodeURIComponent(payload.formOutput.valuesToTransform[input.slug][this.configurationBuilderFormData[payload.uniqueSlug+'-'+input.slug]]);
} else {
formDataForThisPayloadToBuildTheSettingTarget[input.slug] = encodeURIComponent(this.configurationBuilderFormData[payload.uniqueSlug+'-'+input.slug]);
}
}
} else {
formDataForThisPayloadToBuildTheSettingTarget[payloadToAdd.uniqueSlug] = encodeURIComponent(this.configurationBuilderFormData[payload.uniqueSlug+'-value']);
}
payloadToAdd.formOutput.settingTarget = _.trim(templateToUseForSettingTarget(formDataForThisPayloadToBuildTheSettingTarget));
}
if(payload.formInput.type === 'multifield') {
// If the payload's formInput type is multifield, we'll need to combine the values for this payload.
// build a dictionary of formData where each key is the input's slug.

View file

@ -605,6 +605,11 @@
[purpose='option-value'] {
margin-bottom: 32px;
[purpose='nested-label'] {
font-size: 14px;
margin-bottom: 4px;
}
input {
display: flex;
width: 260px;
@ -698,6 +703,9 @@
}
}
[purpose='select-option'] {
max-width: 50%;
}
}
[purpose='name-and-access-type'] {
margin-bottom: 32px;

View file

@ -198,6 +198,7 @@
{{ configurationBuilderFormData[option.uniqueSlug+'-value'] ? 'Yes' : 'No' }}
</label>
</div>
<%// Select input %>
<div purpose="option-value" v-else-if="['select'].includes(option.formInput.type)">
<select :class="[formErrors[option.uniqueSlug+'-value'] ? 'is-invalid' : '']" purpose="access-type-select" v-model.trim="configurationBuilderFormData[option.uniqueSlug+'-value']" class="form-control" style="width: 300px;" @input="clickAutoSelectWhenSet(formInput)">
<option selected value="undefined" >Select an option</option>
@ -205,6 +206,7 @@
</select>
<div class="invalid-feedback" v-if="formErrors[option.uniqueSlug+'-value']">Please select an option</div>
</div>
<%// Multiple inputs %>
<div purpose="option-value" class="w-100" v-else-if="['multifield'].includes(option.formInput.type)">
<div purpose="multifield-option" v-for="input in option.formInput.inputs">
<% // Number input %>
@ -218,8 +220,11 @@
</div>
<% // Text input %>
<div purpose="option-value" v-if="['text'].includes(input.type)">
<input :class="[formErrors[option.uniqueSlug+'-'+input.slug] ? 'is-invalid' : '']" class="form-control" v-model.trim="configurationBuilderFormData[option.uniqueSlug+'-'+input.slug]" :type="input.type" placeholder="Please enter a value" >
<div class="invalid-feedback" v-if="formErrors[option.uniqueSlug+'-'+input.slug]">Please enter a value</div>
<label>
<span purpose="nested-label">{{input.name}}</span>
<input :class="[formErrors[option.uniqueSlug+'-'+input.slug] ? 'is-invalid' : '']" class="form-control" v-model.trim="configurationBuilderFormData[option.uniqueSlug+'-'+input.slug]" :type="input.type" placeholder="Please enter a value" >
<div class="invalid-feedback" v-if="formErrors[option.uniqueSlug+'-'+input.slug]">Please enter a value</div>
</label>
</div>
<% // Radio input %>
<div purpose="option-value" v-else-if="['radio'].includes(input.type)" :class="[formErrors[option.uniqueSlug+'-'+input.slug] ? 'is-invalid' : '']">
@ -238,7 +243,7 @@
{{ configurationBuilderFormData[option.uniqueSlug+'-'+input.slug] ? 'Yes' : 'No' }}
</label>
</div>
<%// Checkbox input %>
<%// Checkbox (with label) input %>
<div purpose="option-value" v-else-if="['booleanWithLabel'].includes(input.type)">
<div class="d-flex flex-row justify-content-between">
<p>{{input.label}}</p>
@ -247,13 +252,13 @@
<span purpose="custom-switch" :class="[configurationBuilderFormData[option.uniqueSlug+'-'+input.slug] ? 'checked' : '']"><span purpose="custom-switch-status"></span></span>
</label>
</div>
</div>
<%// select input %>
<div purpose="option-value" v-else-if="['select'].includes(input.type)">
<div class="d-flex flex-row justify-content-between">
<p>{{input.label}}</p>
<div class="d-flex flex-column">
<select :class="[formErrors[option.uniqueSlug+'-'+input.slug] ? 'is-invalid' : '']" purpose="access-type-select" v-model.trim="configurationBuilderFormData[option.uniqueSlug+'-'+input.slug]" class="form-control" style="width: 120px;">
<div class="d-flex flex-column" purpose="select-option">
<select :class="[formErrors[option.uniqueSlug+'-'+input.slug] ? 'is-invalid' : '']" purpose="access-type-select" v-model.trim="configurationBuilderFormData[option.uniqueSlug+'-'+input.slug]" class="form-control">
<option :value="undefined">Select an option</option>
<option :value="formInput.value" style="text-transform: uppercase;" v-for="formInput in input.options">{{formInput.name}}</option>
</select>