1. Problem Statement and Operational Context
When a WinCC Runtime station is operated from a remote location via Remote Desktop (RDP), the Windows session lifecycle is decoupled from the operator's physical presence. On a Windows server configured with the default Terminal Services policy, a disconnected RDP session is kept alive on the host indefinitely; see the guidance in Troubleshoot unexpected reboots using system event logs for how session state is recorded in the system event logs. The consequence for a WinCC HMI station is that the previously authenticated Windows user remains logged on, and the WinCC operator account remains active, even though no human is at the console. Most plant security policies require the active WinCC user account to be terminated as soon as the physical console is unattended.
The recommended signal source for the trigger is the Windows Event Log, exposed by the Event Viewer (see Event Viewer - Microsoft Learn). Two distinct mechanisms are documented in this article:
- A VBScript inside a WinCC Global Script action that polls the Microsoft-Windows-TerminalServices-LocalSessionManager/Operational channel through the WMI class
Win32_NTLogEventon a 1-second timer. This is the shortest path and is supported on every WinCC V7 installation without a compiler. - A C action that subscribes to the same channel through the native
EvtSubscribeAPI (Windows Event Log API introduced in Vista) and pushes the event into WinCC through an internal tag. This drops the latency from approximately 1 s to under 100 ms at the cost of a compiled DLL.
Both approaches are interoperable with the WinCC Runtime Process Mode (WinCC V7.0 SP3 and later) and the WinCC Runtime Professional (TIA Portal V13 and later).
2. Architecture Overview
The data flow from the RDP event to the WinCC logoff action is summarized in the diagram below. The RDP client (mstsc.exe) closes its window on the remote workstation; Windows writes a record to the LocalSessionManager log; the WinCC Global Script detects it; an internal trigger tag is set; the UserAdmin control bound to that tag executes the standard WinCC logoff routine.
3. Prerequisites
- Siemens WinCC V7.0 SP3 or later, or WinCC Runtime Professional (TIA Portal V13 or later) with the Global Script option licensed.
- Microsoft Windows Server 2012 R2 / 2016 / 2019 / 2022, or Windows 10 / 11. The WinCC installation must be on a Windows version supported by the installed WinCC release; consult the WinCC Installation Notes for the exact matrix.
- Local administrator rights on the WinCC station to enable the LocalSessionManager operational log and to grant the WinCC service read permission on the channel.
- A working RDP listener. Confirm with
netstat -ano | findstr :3389on the station; the listener must show LISTENING. - An internal WinCC tag of type
Binary TagnamedTriggerLogoffdefined in the tag management of the active project. - An internal WinCC tag of type
Text Tag 16namedRDP_LastSeenOffsetused as a high-water mark for the polling script.
4. Windows Event Log Channels Relevant to RDP
The Windows Event Viewer (see Event Viewer - Microsoft Learn) exposes the following channels that are useful for detecting RDP lifecycle events. Only the first two are normally read from inside WinCC.
| Channel | Provider name | When records are written | Default state |
|---|---|---|---|
| Microsoft-Windows-TerminalServices-LocalSessionManager/Operational | Microsoft-Windows-TerminalServices-LocalSessionManager | RDP session create, disconnect, reconnect, logoff at the host level | Enabled on Windows Server; disabled on Windows 10 / 11 |
| Security | Microsoft-Windows-Eventlog | Logon / logoff / credential validation | Enabled |
| System | Service Control Manager | Service state transitions; used as a secondary signal if TerminalServices-LocalSessionManager is disabled | Enabled |
| Application | Various | WinCC Runtime and HMIRuntime application-level events | Enabled |
Event Log Readers group. The LocalSessionManager operational channel requires only read access to the channel SD, which the default WinCC Runtime service identity already holds on Windows Server.5. RDP Event ID Reference
The Microsoft-Windows-TerminalServices-LocalSessionManager/Operational channel is the canonical source. Each RDP lifecycle transition writes exactly one of the Event IDs below.
| Event ID | Written on | Should WinCC act? | Recommended action |
|---|---|---|---|
| 21 | Session logon succeeded (RDP connection accepted) | Optional | Log only; do not log off |
| 22 | Session logoff succeeded | No | No-op: the user has already terminated their session |
| 23 | Session lock | Optional | Lock the WinCC screen instead of logging off |
| 24 | Session unlock | Optional | Require re-authentication |
| 25 | Session reconnection succeeded | No | No-op |
| 26 | Session reconnection disconnected (RDP client closed without logoff) | Yes | Trigger WinCC logoff on the operator whose console session is active |
| 27 | Session reconnection failed | No | Audit-only; route to the WinCC audit log if present |
| 4778 (Security) | A session was reconnected to a WinStation | Optional | Backup signal if LocalSessionManager is disabled |
| 4779 (Security) | A session was disconnected from a WinStation | Optional | Backup signal |
6. Enabling the LocalSessionManager Channel
On Windows 10 / 11 the operational channel is disabled by default. Confirm with eventvwr.msc > Applications and Services Logs > Microsoft > Windows > TerminalServices-LocalSessionManager > Operational. If the channel is disabled, enable it from an elevated command prompt:
wevtutil set-log "Microsoft-Windows-TerminalServices-LocalSessionManager/Operational" /enabled:true
Verify that records are now being written by establishing a test RDP connection from a second PC and inspecting the channel. If the records still do not appear, ensure that the Remote Desktop Services service is running and that the Network Policy Server is not filtering the connection.
7. Reading the Channel with Win32_NTLogEvent (VBScript)
Win32_NTLogEvent is a WMI class that exposes every record in the classical (pre-Vista) channels and, with the appropriate Logfile parameter, every record in the Vista+ operational channels. The schema fields used by the script below are:
| Field | Type | Meaning |
|---|---|---|
| EventCode | String | The numeric Event ID, returned as a string (compare with '26', not 26) |
| Logfile | String | Channel name. For Vista+ channels pass the full path |
| TimeWritten | String | UTC timestamp in YYYYMMDDHHMMSS.XXXXXX+ZZZ format |
| Message | String | Rendered localized event message |
| RecordNumber | UInt32 | Record sequence within the channel |
| SourceName | String | Provider name |
Add the following project function in the WinCC Global Script editor (menu Global Script > Project Functions). It returns the most recent matching event as an array of three elements: EventCode, TimeWritten, Message.
' --- BEGIN ReadLastRDPSessionEvent (project function) -----------------
Option Explicit
Function ReadLastRDPSessionEvent()
Dim objWMIService, colEvents, objEvent
Dim strComputer, strLog, strQuery
Dim dtmLastTime, objLastEvent
strComputer = "."
strLog = "Microsoft-Windows-TerminalServices-LocalSessionManager/Operational"
strQuery = "Select * From Win32_NTLogEvent Where " & _
"Logfile='" & strLog & "' And " & _
"(EventCode='21' Or EventCode='22' Or EventCode='26')"
Set objWMIService = GetObject("winmgmts:{impersonationLevel=impersonate,(Security)}!\\\\" & _
strComputer & "\\root\\cimv2")
Set colEvents = objWMIService.ExecQuery(strQuery)
dtmLastTime = "00000000000000.000000+000"
Set objLastEvent = Nothing
For Each objEvent In colEvents
If CDate(Mid(objEvent.TimeWritten, 1, 14)) > _
CDate(Mid(dtmLastTime, 1, 14)) Then
Set objLastEvent = objEvent
dtmLastTime = objEvent.TimeWritten
End If
Next
If Not objLastEvent Is Nothing Then
ReadLastRDPSessionEvent = Array(CStr(objLastEvent.EventCode), _
CStr(objLastEvent.TimeWritten), _
CStr(objLastEvent.Message))
Else
ReadLastRDPSessionEvent = Array("", "", "")
End If
End Function
' --- END --------------------------------------------------------------
Typical execution time on a local query against the LocalSessionManager channel is 80 - 250 ms on a quad-core WinCC station. If the channel holds tens of thousands of records, query time grows linearly; on stations that have run for several years, consider increasing the WMI repository size or pruning the channel with wevtutil cl Microsoft-Windows-TerminalServices-LocalSessionManager/Operational during a planned maintenance window.
8. Native Vista+ API from a C Action (Latency-Sensitive Variant)
When the 1-second polling latency is too long, replace the WMI call with a native subscription through EvtSubscribe. The required headers and library are wevtapi.h and wevtapi.lib, both shipped with the Windows SDK. The C snippet below is suitable for inclusion in a WinCC ODK external function.
// --- BEGIN EvtSubscribe from a WinCC C action -----------------------
#include <windows.h>
#include <winevt.h>
#pragma comment(lib, "wevtapi.lib")
static EVT_HANDLE g_hSubscription = NULL;
static DWORD WINAPI SubscriptionCallback(EVT_SUBSCRIBE_CALLBACK_FLAG flags,
void* ctx, EVT_HANDLE hEvent) {
PEVT_VARIANT pProps = NULL;
DWORD dwCount = 0, dwBuf = 0;
EvtRender(hEvent, EvtRenderEventXml, 0, NULL, &dwBuf, NULL);
// allocate buffer of dwBuf bytes, EvtRender again, parse <EventID>26</EventID>
// when matched, post a message to WinCC through an internal tag
return 0;
}
extern "C" __declspec(dllexport) void __stdcall StartRDPSubscription() {
LPCWSTR szChannel =
L"Microsoft-Windows-TerminalServices-LocalSessionManager/Operational";
LPCWSTR szQuery = L"*[System[EventID=26]]";
g_hSubscription = EvtSubscribe(NULL, NULL, szChannel, szQuery,
NULL, NULL,
(EVT_SUBSCRIBE_CALLBACK)SubscriptionCallback,
EvtSubscribeStartAtOldestRecord);
}
extern "C" __declspec(dllexport) void __stdcall StopRDPSubscription() {
if (g_hSubscription) { EvtClose(g_hSubscription); g_hSubscription = NULL; }
}
// --- END -------------------------------------------------------------
Compile as a 32-bit DLL on a 32-bit WinCC station or as a 64-bit DLL on a 64-bit WinCC Professional station, then register it as an ODK external function. The push path keeps end-to-end latency from RDP disconnect to WinCC logoff under 100 ms.
9. Building the WinCC Logoff Action
There is no public WinCC VBScript method that logs off the current user directly. The supported pattern is to flip an internal tag that is bound to the UserAdmin control's logoff routine. The required objects in the WinCC project are:
- An internal binary tag
TriggerLogoffwith initial value 0. - A UserAdmin control (Siemens WinCC option, installed by default) placed on the start screen, configured with the Logoff button visible and its Click event bound to a Global Script action called
PerformLogoff. - A second internal binary tag
TriggerLogoffAckthat the action writes back to 0 after the logoff completes, to prevent re-entry.
' --- BEGIN PerformLogoff (Global Script action) -----------------------
Sub PerformLogoff()
On Error Resume Next
HMIRuntime.Trace "RDP disconnect detected at " & Now & vbCrLf
' Pulse the trigger tag; the UserAdmin control logoff routine reacts
' to the rising edge.
HMIRuntime.Tags("TriggerLogoff").Write 1
' Acknowledge after a short delay so the next event can fire again.
Dim dteAck
dteAck = DateAdd("s", 2, Now)
Do While Now < dteAck
DoEvents
Loop
HMIRuntime.Tags("TriggerLogoff").Write 0
HMIRuntime.Trace "Logoff routine returned at " & Now & vbCrLf
End Sub
' --- END --------------------------------------------------------------
HMIRuntime.Stop from the logoff action. Stop terminates WinCC Runtime entirely and will corrupt any open archive segments that have not yet been flushed. Use only the user-level logoff mechanism described above. A full WinCC Runtime shutdown must be performed from the operator's exit sequence, not from an unattended disconnect handler.10. Combining Polling and Logoff in a Cyclic Action
Open the Global Script editor, right-click Actions, and create a new action of trigger type On time. Set the trigger interval to 1000 ms and assign the project function CheckRDPSession. The action maintains a high-water mark in the tag RDP_LastSeenOffset so that the same event is never processed twice.
' --- BEGIN CheckRDPSession (cyclic, 1 s trigger) ----------------------
Sub CheckRDPSession()
Dim arrEvent, intCode, strTime
arrEvent = ReadLastRDPSessionEvent()
intCode = arrEvent(0)
strTime = arrEvent(1)
If intCode = "" Then Exit Sub
' Compare against the cached timestamp; ignore events we have already seen.
If StrComp(strTime, HMIRuntime.Tags("RDP_LastSeenOffset").Read, _
vbBinaryCompare) = 0 Then Exit Sub
HMIRuntime.Tags("RDP_LastSeenOffset").Write strTime
Select Case CLng(intCode)
Case 26
HMIRuntime.Trace "Event 26 (RDP disconnect) - triggering logoff" & vbCrLf
PerformLogoff()
Case 22
HMIRuntime.Trace "Event 22 (RDP logoff) - no action" & vbCrLf
Case 21
HMIRuntime.Trace "Event 21 (RDP logon) - no action" & vbCrLf
End Select
End Sub
' --- END --------------------------------------------------------------
The 1-second trigger interval is configurable. Values as low as 250 ms are practical; below that, the WMI query overhead starts to dominate the polling cycle and the alternative push subscription (Section 8) should be considered.
11. State Diagram: RDP Session vs. WinCC User
The interaction between the RDP session state and the WinCC user state is shown below. The terminal node "WinCC logged off" is reachable only through Event 26 (RDP disconnect) or Event 22 (explicit RDP logoff) handled by the Global Script.
12. Edge Case: Multiple Concurrent RDP Sessions
A WinCC engineering station frequently has more than one RDP connection open at a time (one for the operator, one for the engineer). If the engineer disconnects while the operator is still working at the console, Event 26 fires for the engineer's session only and the operator must not be logged off. Filter the query by user name to ensure that only the operator's disconnect triggers the action.
strOperatorUser = CreateObject("WScript.Shell").ExpandEnvironmentStrings("%USERNAME%")
strQuery = "Select * From Win32_NTLogEvent Where " & _
"Logfile='" & strLog & "' And " & _
"EventCode='26' And " & _
"Message LIKE '%" & strOperatorUser & "%'"
Substitute strOperatorUser at runtime with the Windows account that owns the physical console session. The variable expands to the user under which the WinCC Runtime service is running, which is normally the same as the interactive console user on a dedicated HMI station.
13. Edge Case: Channel Permission for the WinCC Service Account
When the WinCC Runtime service is configured to run under a domain account (a recommended hardening for production stations), that account must hold read access to the operational channel. The default security descriptor on the channel grants read to BUILTIN\\Administrators, BUILTIN\\Event Log Readers, and the local SYSTEM account only. Add the service account to the local Event Log Readers group, or modify the channel SD directly with wevtutil:
wevtutil sl Microsoft-Windows-TerminalServices-LocalSessionManager/Operational /ca:O:BAG:SYD:(A;;0x1;;;S-1-5-32-573)
SID S-1-5-32-573 is the well-known SID for the Event Log Readers built-in group. Restart the WinCC Runtime service after modifying the channel SD so that the cached access token is refreshed.
14. Archive and Recipe Safety Considerations
The archive writer inside WinCC Runtime runs under the WinCC Runtime service identity, not under the logged-in user. A user logoff does not interrupt the archive writer and does not require any special handling. The following scenarios, however, do require care:
- Recipe in progress. If the operator is in the middle of writing a recipe dataset when the RDP disconnect fires, the logoff routine will discard the in-progress recipe. To prevent data loss, bind the recipe write to a Commit event that writes a snapshot to a process tag and reads it back on the next logon.
- Long-running batch step. If a batch step has been started and is waiting for operator confirmation, the logoff routine should mark the step as Interrupted by Logoff rather than Aborted. This is achieved by writing the batch ID to an internal tag before the logoff is triggered.
- Pending alarm acknowledgement. Pending alarms that have not been acknowledged must remain pending after the logoff; the alarm logging subsystem does not depend on the logged-in user.
HMIRuntime.Stop or any function that triggers a Runtime shutdown from the disconnect handler. The WinCC archive writer must always be terminated through the dedicated WinCC exit sequence, which flushes the active archive segments in a controlled manner. A premature shutdown will leave the most recent archive segment in a state that requires a manual rebuild on the next start.15. Field Commissioning Procedure
Use the following checklist during commissioning. Each item must be verified before the station is handed over to operations.
- Confirm the LocalSessionManager operational channel is enabled and that records are visible in
eventvwr.msc. - Define the two internal tags
TriggerLogoffandRDP_LastSeenOffsetin the active WinCC project. - Place a UserAdmin control on the start screen and bind the Logoff button's Click event to the
PerformLogoffGlobal Script action. - Add the two project functions (
ReadLastRDPSessionEvent,PerformLogoff) and one cyclic action (CheckRDPSession) to the project. - Activate the project on the Runtime station.
- Open a second RDP session from a remote workstation and log on with any Windows account.
- Verify in the WinCC diagnostic log (
C:\ProgramData\Siemens\Automation\WinCC\diagnose\WinCC_Sys_<n>.log) that RDP logon - no action appears within 2 seconds of the connection being established. - Close the RDP window without logging off. Verify that Event 26 (RDP disconnect) - triggering logoff appears in the diagnostic log within 2 seconds and that the WinCC screen returns to the standard Logon dialog.
- Confirm via User Administration that the user that was active at the console is no longer listed.
- Repeat steps 6 - 9 with the RDP window open for 30 minutes, then closed. Verify that no spurious logoff is triggered.
- Repeat steps 6 - 9 with two concurrent RDP sessions (one from the operator, one from the engineer). Verify that only the operator's disconnect triggers the logoff.
16. Verification Checks
The following checks confirm correct operation without disturbing the active Runtime. They are suitable for inclusion in a periodic maintenance procedure.
| Check | Expected result | How to verify |
|---|---|---|
| Event 26 is logged on RDP disconnect | Record visible in eventvwr.msc within 1 s |
Open the LocalSessionManager operational log and inspect the latest record |
| Polling script runs at the configured interval | Trace line written every 1 s when an event is detected | Tail the diagnostic log during a test disconnect |
| TriggerLogoff tag toggles | Tag value transitions 0 -> 1 -> 0 on each event | Open the tag online view in WinCC Explorer |
| WinCC logoff completes | Logon dialog appears within 3 s of disconnect | Visual inspection during the test disconnect |
| Archive segments remain intact | No archive rebuild required on next start | Stop and start the Runtime; confirm no alarm about corrupt archives |
| Multiple RDP sessions only log off the operator | Engineer's disconnect does not trigger the operator's logoff | Filter the WMI query by user name (Section 12) |
17. Troubleshooting Matrix
| Symptom | Likely cause | Remediation |
|---|---|---|
| Action runs but no event is returned | LocalSessionManager channel disabled | Enable with wevtutil sl Microsoft-Windows-TerminalServices-LocalSessionManager/Operational /e:true
|
| Action returns Access Denied | WinCC service account not in Event Log Readers | Add the account to the local Event Log Readers group |
| Logoff triggers on every operator action | TriggerLogoff tag not reset to 0 | Confirm TriggerLogoff is written back to 0 inside PerformLogoff
|
| Logoff never triggers | 1-s timer not started, or project function not assigned to the action | Open the action properties and confirm the trigger interval and the project function name |
| WMI query takes > 2 s | Channel contains millions of records; WMI repository rebuild | Limit query to LocalSessionManager only, or archive and clear the channel |
| Event 26 fires but WinCC user remains | UserAdmin control logoff routine did not run | Confirm that the UserAdmin control is on the active start screen and that its Logoff button event is bound |
| Operator logged off when engineer disconnects | Multi-session filter not applied | Add the Message LIKE '%<operator>%' predicate (Section 12) |
| Logoff fires twice on a single disconnect | Polling script reads the same event twice because the timestamp cache is wrong | Cache TimeWritten, not RecordNumber, because record numbers rotate after channel clearing |
| Logoff not visible to operators | Active project differs from the one the action is bound to | Confirm that the WinCC project loaded at Runtime contains the action |
18. Alternative: PowerShell Sidecar Service
On stations where the WinCC Global Script editor is restricted (e.g., lockdown images), a small PowerShell script running as a Windows service can be used as a sidecar that pushes the trigger into WinCC through the OPC UA server of WinCC. The script uses the same Microsoft-Windows-TerminalServices-LocalSessionManager/Operational channel, this time through the Get-WinEvent cmdlet:
$filter = @{
LogName = 'Microsoft-Windows-TerminalServices-LocalSessionManager/Operational'
Id = 26
}
Register-WinEvent -SourceIdentifier RDPDisconnect
-Action {
$payload = @{ trigger = 1 } | ConvertTo-Json
Invoke-RestMethod -Uri "http://localhost:48020/api/v1/tags/TriggerLogoff" `
-Method Put -Body $payload -ContentType 'application/json'
} -FilterHashtable $filter
The Invoke-RestMethod call targets the WinCC REST interface introduced in WinCC V7.4 SP1, which exposes every internal tag at /api/v1/tags/<name>. The sidecar approach is useful when the polling script inside WinCC cannot be edited because the project is signed or supplied by a vendor.
19. Frequently Asked Questions
Which Windows Event ID most reliably indicates that the physical console is unattended?
Event ID 26 from the Microsoft-Windows-TerminalServices-LocalSessionManager/Operational log. It is written every time an RDP session is disconnected from the client side without a Windows logoff. Pair it with Event ID 22 if you also want to log off the WinCC user when an explicit Windows logoff is performed from inside the RDP window.
Can I read the Event Log without polling on a 1-second timer?
Yes. Subscribe to the channel through the EvtSubscribe API from a C action and post the event payload back to a Global Script callback through an internal tag. The end-to-end latency drops to under 100 ms. The polling approach, however, has the advantage that no C compiler, no DLL registration, and no WinCC ODK project is required.
Does the WinCC Runtime service have permission to read the LocalSessionManager log?
On Windows Server with the default service account LocalSystem, read permission is granted. On hardened installations where the WinCC Runtime service runs under a domain account, add that account to the local Event Log Readers group, or grant the read ACE directly through the channel SD using wevtutil sl Microsoft-Windows-TerminalServices-LocalSessionManager/Operational /ca:O:BAG:SYD:(A;;0x1;;;S-1-5-32-573).
What happens to WinCC Runtime archives if the user is logged off in the middle of a recipe step?
The archive write is performed by the WinCC Runtime service, not by the logged-in user. A user logoff does not interrupt the archive writer. A WinCC Runtime shutdown, on the other hand, does flush and close every archive segment and must therefore be triggered explicitly and only from the WinCC exit sequence, never from the RDP disconnect handler.
Is there a way to distinguish a remote disconnect from a local console lock?
Yes. Inspect the SessionID field inside the Event Data section of the rendered XML. If the SessionID matches the active console session (typically 1 on a dedicated HMI station), the event was triggered by the physical console. Otherwise, the event was triggered by an RDP session and should be used as the logoff trigger only when filtered against the operator's Windows account name.