I manage a small fleet of Windows field laptops with built-in cellular modems. They leave the office on the corporate APN and come back months later — usually after a driver update, a Windows feature update, or someone reseating the SIM — quietly sitting on the carrier’s consumer APN instead. The machine happily reports Connected. It even passes traffic. It just cannot reach anything on the corporate network: no VPN, no telemetry, no remote management. You find out from an angry phone call, never from the machine itself.
Manually fixing it is easy once you know the netsh mbn dance. The hard part is noticing. That is what this post is about: a small PowerShell watchdog, run by Task Scheduler every five minutes, that checks the active Access Point Name and — when it has drifted, or the link is down — reinstalls the APN profile from an embedded XML and reconnects. One script, one scheduled task, one log file.
Quick answer
Install your APN profile once by hand with netsh mbn (XML profile, mark it default, connect, verify). Then drop watchdog.ps1 into C:\ProgramData\CellWatch and register it as a scheduled task running as SYSTEM every 5 minutes. Each tick it reads the active connection via netsh mbn show connection, compares the Access Point Name against your expected corporate APN, and on mismatch it rewrites the profile from the XML embedded in the script and reconnects. Progress and failures go to C:\ProgramData\CellWatch\watchdog.log.
Prerequisites and scope
- Windows 10 or 11 with a WWAN modem. USB dongles and internal M.2/PCIe modules both expose the netsh mbn interface.
- Administrator rights for the one-time setup. The watchdog itself runs as SYSTEM.
- PowerShell 5.1 or later, which ships with Windows. Nothing to install.
- The WWAN AutoConfig service (WwanSvc) must be able to run. The script starts it if it finds it stopped.
- Your carrier’s APN string, plus PAP/CHAP credentials if the APN requires them.
Scope note: this covers APN correctness and reconnection. It does not fix a PIN-locked SIM, a hardware radio kill switch, or a carrier outage — but it logs those states so you see them instead of guessing.
Step 1: Find the cellular interface name
Open an elevated Command Prompt or PowerShell. Every netsh mbn command in this post needs Administrator.
netsh mbn show interfacesUse the Name value — often Cellular, but it can be localized on non-English installs. Replace Cellular with yours in every command below.
Also note the IMSI while you are here, in case the profile add later complains about a missing Subscriber ID:
netsh mbn show readyinfo interface="Cellular"Step 2: Build the APN profile XML
Windows has no one-line set-APN switch. You describe the APN as a mobile broadband XML profile and install it with netsh mbn. Save the following as C:\Temp\rogers-myAPN.xml, adjusted to your carrier:
<?xml version="1.0"?>
<MBNProfile xmlns="http://www.microsoft.com/networking/WWAN/profile/v1">
<Name>Rogers myAPN</Name>
<Description>Rogers corporate APN</Description>
<IsDefault>true</IsDefault>
<ProfileCreationType>UserProvisioned</ProfileCreationType>
<AutoConnectOnInternet>false</AutoConnectOnInternet>
<ConnectionMode>auto</ConnectionMode>
<Context>
<AccessString>myAPN.rogers.apn</AccessString>
<Compression>DISABLE</Compression>
<AuthProtocol>NONE</AuthProtocol>
</Context>
</MBNProfile>A few notes on the fields that matter:
- AccessString is the APN. Here myAPN.rogers.apn is a private corporate APN, not the consumer ltemobile.apn — that distinction is exactly what the watchdog guards.
- IsDefault true makes this the default profile. Only one profile per SIM can be the default.
- ConnectionMode auto reconnects on its own. Use manual if you want on-demand connections only.
If your carrier gave you PAP/CHAP credentials, swap the Context block for this one:
<Context>
<AccessString>myAPN.rogers.apn</AccessString>
<Compression>DISABLE</Compression>
<AuthProtocol>PAP</AuthProtocol>
<UserLogonCred>
<UserName>YOURUSER</UserName>
<Password>YOURPASS</Password>
</UserLogonCred>
</Context>AuthProtocol can be NONE, PAP, CHAP, or MsChapV2. If the add fails mentioning Subscriber ID, add a SubscriberID element with the IMSI from step 1 inside the profile and try again.
One thing worth knowing: the file on disk is a throwaway. The watchdog script in Step 4 embeds this same XML and rewrites C:\Temp\rogers-myAPN.xml before every repair, so you never maintain the file by hand.
Steps 2 and 3 exist for one reason only: first-run validation. Install and connect manually once to prove the XML actually works, then hand the job to the scheduled task. Once the deployment is verified, you can delete the temp file — the watchdog recreates it the next time it repairs.
Step 3: Install the profile by hand and verify
Install the profile and prefer user-provisioned profiles for connection decisions:
netsh mbn add profile interface="Cellular" name="C:\Temp\rogers-myAPN.xml"
netsh mbn set highestconncategory interface="Cellular" highestcc=userIf a previous default profile blocks the add, list the profiles and delete the old one first, then add yours again:
netsh mbn show profiles interface="Cellular"
netsh mbn delete profile interface="Cellular" name="OldProfileName"Now connect. There are two flavors — persistent, which uses the profile name stored from the XML, and one-shot, which connects straight from the file without storing a profile:
netsh mbn connect interface="Cellular" connmode=name name="Rogers myAPN"
netsh mbn connect interface="Cellular" connmode=tmp name="C:\Temp\rogers-myAPN.xml"Keep it sticky across reboots and sleep/wake:
netsh mbn set acstate interface="Cellular" state=autoon
netsh mbn set dataenablement interface="Cellular" profileset=internet mode=yes
netsh mbn set powerstate interface="Cellular" state=onVerify before you automate anything:
netsh mbn show profiles interface="Cellular"
netsh mbn show connection interface="Cellular"
netsh mbn show profilestate interface="Cellular" name="Rogers myAPN"You want to see Access Point Name = myAPN.rogers.apn and the profile listed as the default. Do this manual pass once. I never automate a profile I have not connected with by hand first — the watchdog is only as correct as the XML you embed in it.
Step 4: Write the watchdog script
The script’s job is deliberately narrow: check, and fix only what it understands. Each run lands in one of three states:
- Connected with the right APN — write one log line and exit.
- Right APN, but the link is down — reconnect.
- Wrong APN, or no APN visible at all — rewrite the profile from the embedded XML, mark it default, reconnect, and log the outcome.
Save it as C:\ProgramData\CellWatch\watchdog.ps1. ProgramData is readable by the SYSTEM account the task will use, and it is still there when nobody is logged on.
#Requires -RunAsAdministrator
<#
.SYNOPSIS
Cellular APN watchdog for Windows.
.DESCRIPTION
Checks the WWAN interface every run. If the active Access Point Name
is not the expected corporate APN, or the link is down, it reinstalls
the APN profile from the embedded XML and reconnects.
Intended to run from Task Scheduler as SYSTEM every 5 minutes.
Logs to C:\ProgramData\CellWatch\watchdog.log.
#>
$InterfaceName = 'Cellular' # from: netsh mbn show interfaces
$ExpectedAPN = 'myAPN.rogers.apn'
$ProfileName = 'Rogers myAPN'
$WorkDir = 'C:\ProgramData\CellWatch'
$ProfileXml = Join-Path $WorkDir 'cell-profile.xml'
$LogFile = Join-Path $WorkDir 'watchdog.log'
$ProfileXmlContent = @'
<?xml version="1.0"?>
<MBNProfile xmlns="http://www.microsoft.com/networking/WWAN/profile/v1">
<Name>Rogers myAPN</Name>
<Description>Rogers corporate APN</Description>
<IsDefault>true</IsDefault>
<ProfileCreationType>UserProvisioned</ProfileCreationType>
<AutoConnectOnInternet>false</AutoConnectOnInternet>
<ConnectionMode>auto</ConnectionMode>
<Context>
<AccessString>myAPN.rogers.apn</AccessString>
<Compression>DISABLE</Compression>
<AuthProtocol>NONE</AuthProtocol>
</Context>
</MBNProfile>
'@
function Write-WatchLog {
param([Parameter(Mandatory)][string]$Message)
$stamp = Get-Date -Format 'yyyy-MM-dd HH:mm:ss'
"$stamp $Message" | Add-Content -Path $LogFile -ErrorAction SilentlyContinue
}
function Get-CellularStatus {
$conn = netsh mbn show connection interface="$InterfaceName" 2>&1 | Out-String
$connected = $conn -match 'Connection state\s*:\s*Connected'
$apnOk = $conn -match [regex]::Escape($ExpectedAPN)
if (-not $apnOk) {
# Some netsh builds omit the APN from the connection view,
# so fall back to the stored profile state.
$prof = netsh mbn show profilestate interface="$InterfaceName" name="$ProfileName" 2>&1 | Out-String
$apnOk = $prof -match [regex]::Escape($ExpectedAPN)
}
[pscustomobject]@{
Connected = [bool]$connected
ApnOk = [bool]$apnOk
}
}
function Install-CellProfile {
if (-not (Test-Path $WorkDir)) {
New-Item -ItemType Directory -Path $WorkDir -Force | Out-Null
}
$ProfileXmlContent | Set-Content -Path $ProfileXml -Encoding Ascii -Force
netsh mbn set powerstate interface="$InterfaceName" state=on | Out-Null
$add = netsh mbn add profile interface="$InterfaceName" name="$ProfileXml" 2>&1 | Out-String
if ($add -match '(?i)failed|error') {
# A stale profile with the same name can block the add; replace it.
netsh mbn delete profile interface="$InterfaceName" name="$ProfileName" | Out-Null
netsh mbn add profile interface="$InterfaceName" name="$ProfileXml" | Out-Null
}
netsh mbn set highestconncategory interface="$InterfaceName" highestcc=user | Out-Null
}
function Connect-Cellular {
netsh mbn set acstate interface="$InterfaceName" state=autoon | Out-Null
netsh mbn connect interface="$InterfaceName" connmode=name name="$ProfileName" | Out-Null
}
# --- main ---
if (-not (Test-Path $WorkDir)) {
New-Item -ItemType Directory -Path $WorkDir -Force | Out-Null
}
Write-WatchLog '--- watchdog tick ---'
$wwan = Get-Service -Name WwanSvc -ErrorAction SilentlyContinue
if ($wwan -and $wwan.Status -ne 'Running') {
Write-WatchLog 'WWAN AutoConfig not running; starting it.'
Start-Service -Name WwanSvc -ErrorAction SilentlyContinue
}
$status = Get-CellularStatus
if ($status.Connected -and $status.ApnOk) {
Write-WatchLog "OK: connected via $ExpectedAPN."
}
elseif ($status.ApnOk) {
Write-WatchLog 'APN correct but link is down; reconnecting.'
Connect-Cellular
Start-Sleep -Seconds 10
$retry = Get-CellularStatus
Write-WatchLog ("Reconnect result: connected={0} apnOk={1}." -f $retry.Connected, $retry.ApnOk)
}
else {
Write-WatchLog ("APN mismatch or unknown (expected {0}); reinstalling profile." -f $ExpectedAPN)
Install-CellProfile
Connect-Cellular
Start-Sleep -Seconds 15
$retry = Get-CellularStatus
Write-WatchLog ("Repair result: connected={0} apnOk={1}." -f $retry.Connected, $retry.ApnOk)
}Two design decisions worth calling out. First, the check compares plain text — it looks for the expected APN string in the netsh output rather than parsing columns, and it falls back from the connection view to the profile state view. netsh output wording varies a little between Windows builds, and plain substring matching survives that. Second, the XML lives inside the script. The watchdog must be able to repair a machine whose profile was deleted or replaced; if the XML were only a loose file that someone might clean up, the repair path would break exactly when you need it.
Step 5: Schedule it with Task Scheduler
Register the task to run every 5 minutes as SYSTEM, whether or not anyone is logged on:
$action = New-ScheduledTaskAction -Execute 'powershell.exe' `
-Argument '-NoProfile -ExecutionPolicy Bypass -File "C:\ProgramData\CellWatch\watchdog.ps1"'
$trigger = New-ScheduledTaskTrigger -Once -At (Get-Date) `
-RepetitionInterval (New-TimeSpan -Minutes 5) `
-RepetitionDuration (New-TimeSpan -Days 3650)
$principal = New-ScheduledTaskPrincipal -UserId 'SYSTEM' `
-LogonType ServiceAccount -RunLevel Highest
Register-ScheduledTask -TaskName 'Cellular APN Watchdog' `
-Action $action -Trigger $trigger -Principal $principal `
-Description 'Checks the WWAN APN and reconnects when it drifts.'Five minutes is the interval I use. Shorter buys you almost nothing — a reconnect takes 10 to 20 seconds anyway — and a 60-second loop just fills the log without making the machine any healthier.
Step 6: Read the log and test it on purpose
The log lives at C:\ProgramData\CellWatch\watchdog.log. A healthy machine writes two lines per tick:
2026-09-29 14:05:02 --- watchdog tick ---
2026-09-29 14:05:02 OK: connected via myAPN.rogers.apn.Do not trust a watchdog you have never seen fire. Test it: change the APN by hand to the wrong one (or delete the profile outright), wait for the next tick, and confirm the log shows the repair and the connection comes back on the corporate APN. Then verify with netsh mbn show connection yourself. If you skip this test, you have a script, not a watchdog.
Why a watchdog instead of a one-time fix
If the profile is correct right now, why not fix it once and walk away? Because in my experience the APN does not drift on a schedule — it drifts on events, and those events are outside your control:
- OS reinstall or disk cloning. A reimaged machine, or a golden image cloned to new hardware, comes up with whatever the Windows carrier database provisions. For Rogers hardware that means ltemobile.apn, not your corporate APN. The clone does not remember your netsh commands.
- Reboot and power loss. Most reboots are harmless, but an unclean shutdown during a modem firmware update — or a driver that re-enumerates the modem on boot — can drop the user profile and let the carrier default take its place.
- Human hands on the SIM. Field users reseat SIMs, swap in a temporary SIM, or move the modem to another laptop. Every re-insertion is a re-provisioning event, and re-provisioning means the carrier default comes back.
- Driver and firmware updates. Windows Update, the OEM updater, or the carrier’s own tooling can replace the WWAN driver. A new driver means a new enumeration, and a new enumeration means the built-in APN database gets another vote.
There is no documented “pin this APN forever” setting in Windows. The closest things are layers, and I run all of them:
- Precedence:
netsh mbn set highestconncategory interface="Cellular" highestcc=usertells Windows to prefer your user-provisioned profile over the operator-provisioned one. This alone stops most day-to-day drift. - Modem NVRAM: if the module accepts AT commands,
AT+CGDCONT=1,"IP","myAPN.rogers.apn"bakes the APN into the hardware default PDP context, so even a wiped profile store comes back right. - Stop the trigger: group policy to exclude drivers from Windows Update removes the most common re-provisioning cause I see in the field.
The watchdog is the layer that covers everything those three cannot: the reinstall you did not plan, the clone you forgot to re-provision, the SIM swap at a remote site. It does not prevent drift; it bounds the damage to five minutes.
And its log is a diagnostic tool, not just an audit trail. Every repair line has a timestamp. Correlate those timestamps with driver installs in Event Viewer (or your RMM), and patterns emerge: repairs every Tuesday at 03:00 mean a scheduled task or update is the culprit. Find it once, kill the trigger, and that class of drift never comes back. The watchdog does not just fix the symptom — it hands you the evidence to fix the cause.
Edge cases, pitfalls, and recovery
Subscriber ID errors: some modems refuse a profile add without the IMSI. Add the SubscriberID element from step 1, install by hand once, and make sure the XML embedded in the script matches what worked.
PAP/CHAP credentials: if your APN needs them, put the UserLogonCred block in the embedded XML too. The script rewrites the whole profile on repair, so anything missing from the embedded copy is lost at exactly the wrong moment.
PIN-locked SIM: the script cannot enter a PIN. If the SIM needs one, disable the PIN on the SIM or handle the unlock separately. The log will show the repair failing, which is still useful signal — better than silence.
Radio off and airplane mode: the repair path sets powerstate on, but it cannot override a hardware radio kill switch. When the log says repair failed two ticks in a row, check the physical switch before anything else.
ExecutionPolicy: the task uses -ExecutionPolicy Bypass for its own process only and changes nothing machine-wide. If your environment forbids even that, sign the script and drop the flag.
Two profiles fighting over default: only one profile per SIM can be the default. If some other tool keeps installing a competing default, the watchdog will keep winning every five minutes and the log will show the pattern — that pattern is your clue to go fix the other tool instead of blaming the modem.
What it deliberately does not do: no reboots, no driver reinstalls, no firmware flashes. A watchdog that reboots machines on its own becomes the outage it was supposed to prevent.
Rollback is one line: Unregister-ScheduledTask -TaskName ‘Cellular APN Watchdog’, then remove C:\ProgramData\CellWatch. The installed APN profile stays until you delete it with netsh mbn delete profile.
Summary and further reading
Windows gives you no single command to pin an APN, but netsh mbn plus a five-minute scheduled script gets you the same result with an audit trail: the profile XML is the desired state, the script is the reconciler, the log is the proof. Install the profile by hand once, embed it in the script, schedule the task as SYSTEM, and then deliberately break the APN to watch the watchdog earn its name.
Further reading: reaching your machines from anywhere — handy for the moment the watchdog log tells you a machine needs hands-on attention.
💬 Comments