Three questions decide this, and only three. Does the client create an operating-system connection entry that survives a reboot? Does it ship a command line that can take a secret from something other than its argument list? Does its identity requirement force a human to answer a browser prompt? Answer them in that order and the choice stops being a matter of taste, because a scheduled job can only be as headless as the weakest of the three answers allows.

Match the client to the three scriptable layers

A native OS profile, a vendor CLI, a GUI-only client and a browser-SSO client need four different approaches. The first two survive unattended reboots and unattended reboots are the whole point. The other two need a design decision before they need a script.

What the client exposesWhat to scriptWhat still bites
OS-native connection entryrasdial, scutil --nc, nmcliTunnel is up before DNS and routes settle
Vendor CLI reading stdinCLI wrapped by the scheduler, secret from the credential storePrompt text changes between releases
GUI onlyPinned version plus a state assertionLocale and DPI break the click targets

Path one: the operating system already owns the profile

This is the cheapest path and the one most people overlook. On Windows, rasdial "Office" connects and rasdial "Office" /disconnect closes it; rasphone -d "Office" drives the same entry through the dial-up UI. macOS lists saved services with scutil --nc list and starts one with scutil --nc start. Linux hands the job to NetworkManager.

rasdial "Office" user *
scutil --nc start "Office"
nmcli connection up id Office
nmcli -t -f GENERAL.STATE connection show Office

These interfaces only know profiles the OS owns, so they cannot drive an arbitrary third-party client. When the profile is missing, create it once and let the script check for it rather than recreating it on every run. Make the script idempotent: read the current state, connect only when disconnected, and then test the private name the job actually needs.

Path two: the vendor ships a command line

Some clients expose enough of a command line to be wrapped: Cisco AnyConnect has had a connect-only helper for years, and OpenConnect speaks AnyConnect, GlobalProtect and Pulse protocols from one binary. The requirement that matters is not the flag list but where the secret comes from, because a password in the argument list is readable by anyone who can list processes.

printf '%s\n' "$VPN_PASS" | openconnect --protocol=anyconnect \
  --user "$VPN_USER" --passwd-on-stdin vpn.example.com

FortiClient and some GlobalProtect builds have no supported CLI at all, and a community client or a vendor-licensed replacement is a procurement decision, not a workaround. In CI, grant the service account rights to that exact binary and not to a shell, so an injected argument cannot become a root command.

Path three: GUI automation, and why it is last

When the official client has no interface, scripts click buttons through window messages or SendKeys. That is version- and locale-fragile, it can leave a half-connected session, and it fails silently when a dialog appears in the wrong language. If you take this route, pin the client version, assert the resulting connection state afterwards, and keep a manual fallback documented next to the script.

The same caution applies to force-disconnecting a client by killing its process. The tunnel can outlive the process, or a supervisor can restart it, so kill-and-restart is not a reliable reset either.

What breaks after the tunnel is up

A successful dial proves authentication and nothing else. Test the resource the job exists to reach, with its own protocol and a short timeout, and name the stage in the failure message. Profiles, certificates and stored credentials visible on your desktop may be invisible to the service session, so run the job once under the scheduler's own account before trusting it.

Bound the retries. A loop that repeats a rejected credential locks accounts, and an MFA prompt cannot be answered by a headless job without an enrollment design; scripted retries against a browser SSO flow simply burn attempts. Prefer an MDM-delivered VPN payload or a configuration profile for a fleet, where the platform owns the credential lifecycle, and keep scripts for the clients that cannot be managed that way.

Log the exit code and the first failing resource, not the fact that the script ran. That single line is what turns a scheduler alert into a five-minute diagnosis.

مۇناسىۋەتلىك ماقالىلەر