Resolving Orphaned WinCC MsgService When Stopping Runtime

David Krause11 min read
SiemensTroubleshootingWinCC
Licensed PE Working through this on a live machine? A Maine-licensed engineer can take it from here — included with IMD hardware, by the hour for everything else. Book an engineer

Problem Overview: Orphaned MsgService After WinCC Explorer Stop

When a WinCC 7.x project opens an alarm-logging message service (MsgService) through the Open Development Kit (ODK) on Graphics Runtime start, the service is intended to live for the full lifetime of the Runtime process. A button on a screen can call MSRTStopMsgService() and then Exit WinCC Runtime to terminate cleanly. The same sequence, however, is not invoked when an operator clicks the square Stop button in the WinCC Explorer toolbar.

The Explorer stop button tears down the Runtime container, discards the message-service handle stored in the project, and exits the RT process — but the MsgService worker process started by MSRTStartMsgService() / MSRTStartMsgServiceNotifyCallback() remains alive in the Windows session. Because the RT-side handle is gone, the next Runtime activation cannot re-attach to the surviving service. Attempts to start a fresh MsgService return an error, the alarm subsystem fails to initialize, and the only documented recovery is a full station reboot.

This is a lifecycle ownership problem, not a corruption problem. The orphan does not damage the SQL-backed alarm archive; it just refuses to release the named pipe / mutex used by Alarm Logging Runtime to talk to its message dispatcher.

Root Cause Analysis: Why the Explorer Stop Button Leaves MsgService Alive

WinCC Runtime is a multi-process application. The components involved in this fault are:

  • WinCC Explorer (container process) — hosts the project database connection, the Graphics Runtime, the alarm logging editor session, and the project lifecycle events.
  • Graphics Runtime (PDL engine) — starts in response to the Explorer and executes C / VBS / ODK actions declared in the project.
  • Alarm Logging Runtime (MsgService) — separate Windows process instantiated by ODK; it owns the in-memory message queue that backs MSrvMsg_* API calls.

The Explorer stop button signals the Graphics Runtime and the project services to shut down. It does not send WM_DESTROY-equivalent cleanup to ODK-allocated resources that were started in user code. Specifically:

  1. MSRTStartMsgService() returned a 32-bit ServiceID that the developer stored only in a local C variable or a non-persistent tag.
  2. When the RT process exits, the variable evaporates. The ODK library cannot match it back to the spawned dispatcher.
  3. The dispatcher process has no parent-monitoring contract — it will not self-terminate just because the spawning thread has gone.
  4. On the next Runtime start, MSRTStartMsgServiceNotifyCallback() is called again. The ODK tries to create a new dispatcher but the named resource is locked by the previous orphan; the call returns a failure code (typically 0x8004...-class COM error or an empty ServiceID).

The reason screen-button shutdown works is that the developer controls the action chain: stop the service, then exit Runtime. The Explorer stop button bypasses any user-authored Exit Runtime actions, so the chain is broken.

Affected Versions and Components

Component Version Behavior
WinCC 7.0 / 7.2 / 7.3 / 7.4 / 7.5 All Same ODK API surface; Runtime persistence tag attribute available from V7.0 SP2 onward.
Alarm Logging Runtime Component of WinCC Provides MSRTStartMsgService, MSRTStartMsgServiceNotifyCallback, MSRTStopMsgService via WCCOAAlarmLogging.h.
WinCC ODK Component of WinCC Statically linked into the project action; functions exported from PDLRTApi.dll / CCAlgHlpd.dll depending on WinCC version.
TIA Portal WinCC Professional / Comfort V13+ Different API: uses HMIRuntime.AlarmLogging .NET methods. Issue is structurally identical; cleanup relies on RT lifecycle hooks rather than Explorer stop semantics.
Compatibility note: The "Runtime persistence" option for internal tags described below is a WinCC 7.x concept. In TIA Portal WinCC Professional the equivalent is the tag property Retain within an HMI tag, and the alarm subscription is closed automatically when the RT process tears down — a fix is usually not required there. Apply the techniques in this article to WinCC 7.x installations only.

Solution 1: Store the MsgServiceID in a Runtime-Persistent Tag

The cleanest fix is to give the dispatcher handle a lifetime that spans Runtime stop / start cycles. WinCC 7.x internal tags can be flagged Runtime persistence: their last written value is retained when Runtime stops and is restored on the next start.

Procedure

  1. In the WinCC Explorer, open Tag Management and create a 32-bit unsigned internal tag, e.g. MsgServiceID.
  2. Open the tag's Properties dialog and switch to the Limits/Reports tab. Tick the option "Runtime persistence" (also labelled Persistenter Wert im Runtime in German locales).
  3. Confirm with OK. The tag database in the project now records the value across RT cycles.
  4. In your C action that calls the ODK start function, assign the returned ServiceID to the tag with SetTagDWord() / SetTagRaw().
  5. At Graphics Runtime start, read the tag back with GetTagDWord(). A non-zero value means a previous session left a dispatcher alive; pass it to MSRTStopMsgService() first, then call MSRTStartMsgServiceNotifyCallback() for a fresh one.

C Action Skeleton

#include "WCCOAAlarmLogging.h"

static DWORD g_ServiceID = 0;

BOOL OnStart(void)
{
    DWORD prior = GetTagDWord("MsgServiceID");
    if (prior != 0)
    {
        // Recover from a previous Explorer-stop orphan
        MSRTStopMsgService(prior);
    }

    g_ServiceID = MSRTStartMsgServiceNotifyCallback(
        /* user data */ 0,
        /* callback  */ &AlarmCallback,
        /* flags     */ 0);

    if (g_ServiceID != 0)
    {
        SetTagDWord("MsgServiceID", g_ServiceID);
        return TRUE;
    }
    return FALSE;
}

BOOL OnStop(void)
{
    if (g_ServiceID != 0)
    {
        MSRTStopMsgService(g_ServiceID);
        SetTagDWord("MsgServiceID", 0);
    }
    return TRUE;
}
The Explorer stop button still will not call OnStop(), but the persistent tag gives the next session the means to recognise the orphan and clean it up. This converts the problem from "Runtime will not start" into "Runtime cleans up at the cost of one extra MSRTStopMsgService call".

Solution 2: External Watchdog via WMI

If you cannot modify the project (third-party delivery, validated system) you can still rescue the station with a host-side process. WMI event subscriptions can watch for the termination of the WinCC Explorer process and run a VBScript that kills the leftover dispatcher.

Install the WMI subscription (elevated PowerShell)

$query = @"
SELECT * FROM __InstanceDeletionEvent
  WITHIN 5
  WHERE TargetInstance ISA 'Win32_Process'
    AND TargetInstance.Name = 'PDLRT.exe'
"@

$action = New-Object System.Management.ManagementEventHandler({
    param($sender, $e)
    Get-WmiObject Win32_Process -Filter "Name='MsgService.exe'" |
        ForEach-Object { $_.Terminate() }
})

Register-WmiEvent -Query $query -Action $action -Namespace "root\cimv2"

This subscription runs in the user session and fires within five seconds of the RT process disappearing. It enumerates any process whose image name matches the alarm dispatcher and calls Terminate(). The WMI subscription itself is persistent across logons; remove it with Unregister-Event after the cleanup completes if you only need a one-time fix.

Edge cases

  • The actual process name varies by WinCC service pack (e.g. CCMsgDisp.exe, MsgDisp.exe). Use a broader filter on the command line (LIKE '%WinCC%Alarm%') if the literal name is unknown.
  • Run the subscription in the same Windows account that owns the Runtime. A SYSTEM-context subscription cannot see the user-mode dispatcher.
  • On a Server Core installation, install the WMI Compatibility feature first; otherwise Get-WmiObject fails with 0x80041010.

Solution 3: ODK Lifecycle via Project Exit Actions

WinCC executes actions in a fixed set of triggers. The Explorer stop button does not fire a project-level "on stop" trigger, but it does fire the standard RT exit event @ExitRT for the Global Script runtime. Configure a C action bound to that trigger to stop any service the project opened.

  1. In the WinCC Explorer, open Global Script > C Editor.
  2. Create a new action and select the trigger Runtime > Exit.
  3. Place the cleanup call in the action body:
#include "apdefap.h"
void OnExit(char* lpszPictureName, char* lpszObjectName, char* lpszPropertyName)
{
    DWORD id = GetTagDWord("MsgServiceID");
    if (id != 0)
    {
        MSRTStopMsgService(id);
        SetTagDWord("MsgServiceID", 0);
    }
}
  1. Compile and assign the action to the @ExitRT trigger in the project's Global Script Runtime settings.

This works in projects that allow the Explorer to drive the standard RT shutdown sequence (i.e. you stop Runtime from the project menu or by closing the Explorer window). The square toolbar Stop button on the project still bypasses @ExitRT on some WinCC 7.0 SP3 builds — verify the trigger fires by enabling a debug printf to the WinCC diagnostic file WinCC_Sys_.log.

Solution 4: Reset_WinCC.vbs Utility

Siemens ships a reset script in every WinCC installation:

C:\Program Files (x86)\Siemens\Automation\WinCC\bin\Reset_WinCC.vbs

It tears down the WinCC service stack, the project database connections, and the alarm subsystem, then restarts the WinCC services. It is the safest "last resort" tool for the orphan problem and is the recommended path for hot-fix support tickets.

Documented side effect: Running Reset_WinCC.vbs while the Runtime is bound to a SIMATIC S7 PLC over TCP disrupts the S7 channel handshake. The connection does not always recover automatically; an entire operator-station reboot may be required. Do not deploy Reset_WinCC.vbs as a routine fix on a live HMI panel — use Solutions 1–3 instead.

Solution 5: Connectivity Pack Polling Alternative

If the only reason for opening a MsgService was to publish alarm state changes to an external HTTP endpoint, you can avoid the dispatcher entirely. The WinCC Connectivity Pack exposes a SOAP / .NET API that queries the archived alarm data directly from SQL:

var connector = new WinCC.Runtime.Connectivity.AlarmProvider();
var filter = new WinCC.Runtime.Connectivity.AlarmFilter
{
    TimeStampFrom = DateTime.UtcNow.AddSeconds(-2),
    StateFilter   = AlarmStateFilter.Active | AlarmStateFilter.CameIn
};

foreach (var alarm in connector.Query(filter))
{
    PostToHttp("https://scada-gw/alarm", alarm);
}

Polling every 1–2 s removes the lifecycle problem: there is no MsgService to orphan, and the application scales across WinCC server / client pairs because the connector talks to the central archive. The trade-off is a small latency penalty and a steady network load proportional to the alarm rate.

Verification Procedures

After applying any of the solutions above, validate the fix on a test station with the following sequence:

  1. Start the WinCC project and confirm MsgServiceID is written to the persistent tag (inspect with WinCC Tag Simulator or the Tags online view).
  2. Trigger a test alarm and confirm the C / VBS callback fires.
  3. Click the square Stop button in the WinCC Explorer. The RT process should exit within 5 s.
  4. Open Task Manager > Details and sort by image name. The dispatcher image (e.g. CCMsgDisp.exe) must not be present.
  5. Start the Runtime again. The dispatcher must start, the alarm subscription must succeed, and the OnStart() C action must return TRUE.
  6. Generate a second alarm to confirm the new dispatcher is functional.
  7. Repeat steps 3–6 at least three times to ensure the recovery is deterministic.

Troubleshooting Matrix

Symptom Likely Cause Fix
RT starts but no alarm callback fires Persistent tag still holds the orphan ID; OnStart called MSRTStopMsgService on it but the start call returned 0 Verify the tag value is set to 0 after stop; check GetLastError() in the start path
RT start fails with "Alarm subsystem not available" Orphaned dispatcher is still holding the named pipe Run Reset_WinCC.vbs or apply WMI watchdog
WMI watchdog fires but the dispatcher is not killed Wrong image name in the filter, or the script runs in SYSTEM context Inspect Get-Process | ? PathName -match 'WinCC'; subscribe in the interactive user session
Reset_WinCC.vbs leaves S7 channel down Channel re-handshake fails after the WinCC services restart Reboot the operator station, or restart S7DOS service manually
Performance drops on the HMI client MsgService is opened and closed on every alarm Adopt the keep-alive pattern: open once on RT start, close once on RT stop
Tag value reverts to 0 every restart Runtime persistence not enabled, or tag is binary type Re-check the tag property sheet; use a 32-bit unsigned internal tag

Best Practices for MsgService Lifecycle Management

  • Open the MsgService exactly once in the project's Global Script > C > Runtime Start trigger. Closing and reopening it for every alarm costs 30–80 ms per cycle and exposes you to the orphan condition on any abnormal termination.
  • Always pair MSRTStartMsgService with a MSRTStopMsgService in the Runtime Exit trigger. Code defensively: check the return code, log to the WinCC diagnostic file, and zero the persistent tag.
  • Keep the ServiceID in a runtime-persistent internal tag, not in a global C variable. C variables evaporate at process exit; the tag is the only durable contract between sessions.
  • Document the dispatcher image name in the project functional specification so that WMI / PowerShell scripts authored by IT do not guess the wrong name.
  • For alarm forwarding to external systems, prefer the Connectivity Pack polling approach over custom ODK + HTTP post. The polling pattern survives Runtime crashes, server failover, and redundant server pairs without any cleanup logic.
  • Schedule a quarterly restart of the WinCC server even with a perfect cleanup, as a defence-in-depth measure against accumulated handle leaks in the WinCC service stack.

Frequently Asked Questions

Why does stopping Runtime from a screen button work but the Explorer Stop button does not?

A screen button executes your C / VBS action, which lets you call MSRTStopMsgService() before ExitWinCCRuntime. The Explorer Stop button is a host-level signal that bypasses user actions; the dispatcher process simply has no shutdown contract with the Explorer, so it keeps running until you reboot the station or use a watchdog.

What is the exact ODK function to start a message service and get its ID?

Call MSRTStartMsgServiceNotifyCallback(userData, callback, flags) from WCCOAAlarmLogging.h. The function returns a 32-bit ServiceID (non-zero on success). Store the ID in a runtime-persistent internal tag, not in a C local, so it survives the Runtime process exit.

Does Reset_WinCC.vbs damage the S7 connection permanently?

No, it does not damage the PLC connection — it tears down the WinCC-side channel handshake. On some WinCC 7.x service packs the channel does not auto-recover after the script finishes, which is why a full operator-station reboot is the documented fallback. Reserve Reset_WinCC.vbs for the cases where Solutions 1–3 cannot be deployed.

Is the runtime-persistent tag option available in TIA Portal WinCC Professional?

Yes, but it works differently. Set the HMI tag property Retain and bind the message service to the HMI tag, then use the HMIRuntime.AlarmLogging .NET methods. TIA Portal closes the alarm subscription automatically when the Runtime tears down, so the orphan problem is rare in that environment.

Can I detect an orphan MsgService from a VBS action on the next Runtime start?

Yes. Read the runtime-persistent internal tag that holds the previous ServiceID. A non-zero value at start-up means a previous session left a dispatcher alive. Pass the ID to MSRTStopMsgService before calling MSRTStartMsgServiceNotifyCallback for a clean service handle.

Back to blog