Scripting a disconnect usually fails for one reason: the script addresses the wrong owner. A tunnel managed by the community OpenVPN GUI belongs to that GUI's session, a tunnel started by a Windows service belongs to the service, and OpenVPN Connect belongs to neither. Work through these checks in order, and stop when the owner is unclear. Sending a kill command because the owner was ambiguous is how one profile disconnection turns into a site outage.

For a GUI-managed connection, the useful command is a message to the GUI rather than a signal to openvpn.exe.

First, identify the owner

Look at which program actually shows the connected state before writing anything. A tray icon means the GUI session owns it. A service entry means the service does. A separate application window with its own profile list means neither, and its own version-specific interface applies.

Check the account and desktop session as well. A scheduled task running as another user, or in a non-interactive session, should not be assumed to control the GUI that a logged-in person can see. That mismatch produces a command that exits cleanly and changes nothing, which is the most expensive kind of failure to debug from a log.

Then send the owner's own disconnect

With the GUI installed at the example path and a profile named office, the whole command is one line:

"C:\Program Files\OpenVPN\bin\openvpn-gui.exe" --command disconnect office

Substitute your real installation path and the exact configuration name, including case as the GUI shows it. The project's command documentation lists the supported GUI controls, and this example belongs to OpenVPN GUI alone — it is not a command for every application with OpenVPN in its name.

For a service-owned tunnel, the correct control is the service, not the GUI binary. Inspect the installed service and the profiles it manages before stopping anything, because a stop can take down several tunnels at once and you will not get a per-profile result back.

Check nothing else goes down

The GUI also documents a disconnect_all command, and its scope is exactly why it is the wrong tool for a script meant to stop only office. If you are tempted to use it for simplicity, ask what else on that machine is connected and how the user will notice.

taskkill /F /IM openvpn.exe is worse in two ways. It selects processes by executable name, and it forces termination, so it expresses neither a profile name nor the owner's normal disconnect sequence. Calling that a graceful shutdown hides both problems behind a single exit status.

Whichever path you take, verify the blast radius before the change: list the connected profiles, note which ones should survive, and check them again afterwards.

Confirm the state, not the exit code

After requesting the disconnect, read the named connection's state through its owner rather than trusting the command's return value. If the next step needs the network after teardown, wait for that state change with an explicit timeout instead of assuming cleanup finished when the process returned. A script that races the teardown will fail intermittently and be blamed on the VPN.

If the state never changes, the owner is probably not the one you addressed. Re-check the session and account before escalating to a stronger command.

What to put in the log

Log the profile name, the owner you addressed and the launch context, plus the resulting state. Do not dump the full configuration or credentials into the task log, and do not log the command line if it contains a path to a file with secrets.

An error message that names the profile and the context is enough for the next person to reproduce the problem. A log that records only a generic exit code sends them back to the beginning, which is where the guessing restarts.

Zvinyorwa zvakafanana