Overview
Siemens WinCC exposes a full VBScript (VBS) runtime that can call out to any COM automation server, including the Microsoft ActiveX Data Objects (ADO) library. The classic transfer tag pattern uses a Boolean internal tag as a handshake signal: a button or external PLC sets the tag to TRUE, the VBS action detects the edge, performs a SQL write, and then writes FALSE back to the tag to arm the next cycle. This pattern decouples HMI graphics from database latency and gives you deterministic, single-row inserts without losing events under fast operator input.
This article covers the working VBS code, the correct way to wire it to a WinCC tag-change event, the ODBC / OLE DB provider choices for SQL Server, the required WinCC and SQL Server configuration, and the failure modes you will hit in the field. It targets WinCC 7.4 / 7.5 SCADA running on Windows, and notes the differences for TIA Portal WinCC Runtime Professional where the API surface is similar but the editor paths differ.
Prerequisites
| Item | Requirement | Notes |
|---|---|---|
| WinCC version | 7.4 SP1, 7.5, or 7.5 SP1 | VBS runtime is built into the WinCC SCADA product; no separate license key required for scripting |
| SQL Server | SQL Server 2016, 2017, 2019, or 2022 | Express edition is acceptable for single-station logging |
| MDAC / OLE DB | SQL Server Native Client 11.0 or Microsoft ODBC Driver 17/18 for SQL Server | WinCC host must be able to resolve the SQL host by DNS / NetBIOS name or IP |
| WinCC project rights | Computer-level configuration access | Required to register an action and create internal tags |
| VBS editor | WinCC Graphics Designer -> right-click object -> Properties -> Events | Or use the C-script editor for C equivalents; the VBS path is the one shown below |
System.Data.SqlClient). You must use COM-style ADODB.Connection and ADODB.Recordset objects.Architecture: The Transfer Tag Handshake
The transfer tag is a Boolean internal tag (typically named DB_TransferTrigger) acting as a single-bit semaphore. The control flow is:
- Operator presses a button on the WinCC screen. The button's Click event writes TRUE to
DB_TransferTrigger. - A WinCC action is configured with the OnTagChange trigger on
DB_TransferTrigger. The action is a VBScript procedure that runs in the WinCC background. - When the action fires and sees
DB_TransferTrigger.Value = TRUE, it reads the value tags, opens an ADODB connection, executes anINSERT, closes the connection, then writes FALSE back to the trigger tag. - If the operator presses the button twice in quick succession, the second event is latched in the tag and the action will fire again as soon as the first insert returns and the tag is re-armed to FALSE then back to TRUE.
Step 1: Create the Transfer Tag and Value Tags
In the WinCC Explorer open Tag Management -> Internal Tags and create:
| Tag name | Data type | Purpose |
|---|---|---|
DB_TransferTrigger |
Binary tag (BOOL) | Handshake bit; action is triggered on change |
DB_PartNumber |
Text tag, 8-bit char, length 32 | Value to write to SQL column PartNumber
|
DB_Quantity |
Signed 32-bit (DWORD signed) | Value to write to SQL column Quantity
|
DB_Operator |
Text tag, length 16 | Operator login name (optional) |
DB_TransferStatus |
Signed 32-bit | 0 = idle, 1 = writing, -1 = error, status word for diagnostics |
If the value tags are to be populated from the PLC, configure them as external tags on the appropriate PLC connection (S7-300/400/1200/1500) and have WinCC poll them with the default 1 s update cycle, or with the cyclic trigger the project uses for production tags.
Step 2: Prepare the SQL Server Target
Create the destination database and table on the SQL Server host. A minimal but realistic schema is shown below; adapt column names and types to your actual process values.
CREATE DATABASE WinCC_Logging;
GO
USE WinCC_Logging;
GO
CREATE TABLE dbo.ProductionLog (
LogID BIGINT IDENTITY(1,1) PRIMARY KEY,
PartNumber NVARCHAR(32) NOT NULL,
Quantity INT NOT NULL,
Operator NVARCHAR(16) NULL,
TransferTime DATETIME2(0) NOT NULL CONSTRAINT DF_ProductionLog_TransferTime DEFAULT (SYSUTCDATETIME()),
StationName NVARCHAR(32) NULL
);
CREATE INDEX IX_ProductionLog_TransferTime ON dbo.ProductionLog (TransferTime DESC);
GO
CREATE USER [WINCC\WinCCService] FOR LOGIN [WINCC\WinCCService];
GRANT INSERT ON dbo.ProductionLog TO [WINCC\WinCCService];
GRANT SELECT ON dbo.ProductionLog TO [WINCC\WinCCService];
GO
Use a dedicated SQL login with the minimum privilege set: db_datawriter on the target table is sufficient. Do not use sa; the connection string will be readable in WinCC diagnostics and the WinCC service account is a common pivot point in incident-response investigations.
Step 3: Configure the ODBC / OLE DB Provider
WinCC VBS opens SQL Server through OLE DB or through an ODBC DSN. The DSN-less connection string is preferred because it survives SQL client upgrades and avoids the 32-bit / 64-bit DSN mismatch on 64-bit WinCC hosts. The recommended provider is the Microsoft OLE DB Driver for SQL Server (MSOLEDBSQL) installed alongside the Microsoft OLE DB Driver for SQL Server package.
' Connection string variants (pick one)
' Option A: OLE DB Driver 18 with TLS 1.2
"Provider=MSOLEDBSQL;Server=SQLHOST\SQLEXPRESS;Database=WinCC_Logging;Uid=wincc_app;Pwd=StrongPwd!2024;Encrypt=Yes;TrustServerCertificate=No;"
' Option B: Legacy SQLOLEDB (deprecated, avoid on SQL 2019+)
"Provider=SQLOLEDB.1;Data Source=SQLHOST;Initial Catalog=WinCC_Logging;User ID=wincc_app;Password=StrongPwd!2024;"
' Option C: ODBC DSN (only if a 32-bit DSN is installed on a 32-bit WinCC host)
"DSN=WinCC_SQL;Uid=wincc_app;Pwd=StrongPwd!2024;"
odbcad32.exe located in C:\Windows\SysWOW64\odbcad32.exe. The 64-bit one in System32 will not be visible to the WinCC service if WinCC itself runs as a 32-bit process.Step 4: Write the VBScript Action
Open the WinCC Graphics Designer and place a button on a screen. Right-click the button, choose Properties -> Events -> Click, and use the action button to create a new VBS action. Then in the Computer editor (or directly via the project tree) create a global action with the trigger OnTagChange -> DB_TransferTrigger. Paste the following code into the action body.
'================================================================
' WinCC VBS action: TransferTag_SQLInsert
' Trigger: OnTagChange on internal tag DB_TransferTrigger
' Writes one row to dbo.ProductionLog per rising edge
'================================================================
Option Explicit
' --- Local references to WinCC runtime API -----------------------
Dim oTrigger, oValueTag, oStatusTag
Set oTrigger = HMIRuntime.Tags("DB_TransferTrigger")
Set oValueTag = HMIRuntime.Tags("DB_PartNumber")
Set oStatusTag = HMIRuntime.Tags("DB_TransferStatus")
' --- Read latest values from the tag system ----------------------
oTrigger.Read
oValueTag.Read
HMIRuntime.Tags("DB_Quantity").Read
HMIRuntime.Tags("DB_Operator").Read
' Only act on the rising edge (TRUE)
If oTrigger.Value <> True Then
Exit Sub
End If
' Mark busy so the HMI can show a spinner / disable the button
oStatusTag.Write 1
' --- Build the parameter set ------------------------------------
Dim sPart, iQty, sOp, sStation
sPart = CStr(HMIRuntime.Tags("DB_PartNumber").Value)
iQty = CLng(HMIRuntime.Tags("DB_Quantity").Value)
sOp = CStr(HMIRuntime.Tags("DB_Operator").Value)
sStation = "Line1"
' --- Sanitise: single quotes must be doubled for T-SQL ----------
Dim sPartEsc
sPartEsc = Replace(sPart, "'", "''")
' --- Open ADODB connection ---------------------------------------
Dim conn, cmd, strSQL
On Error Resume Next
Set conn = CreateObject("ADODB.Connection")
conn.ConnectionTimeout = 5
conn.CommandTimeout = 10
conn.Open "Provider=MSOLEDBSQL;Server=SQLHOST\SQLEXPRESS;Database=WinCC_Logging;Uid=wincc_app;Pwd=StrongPwd!2024;Encrypt=Yes;TrustServerCertificate=No;"
If Err.Number <> 0 Then
HMIRuntime.Trace "DB_TransferTrigger: connection open failed: " & Err.Number & " " & Err.Description & vbCrLf
oStatusTag.Write -1
Err.Clear
On Error Goto 0
Exit Sub
End If
' --- Parameterised INSERT (preferred) ---------------------------
strSQL = "INSERT INTO dbo.ProductionLog (PartNumber, Quantity, Operator, StationName) " & _
"VALUES (?, ?, ?, ?)"
Set cmd = CreateObject("ADODB.Command")
Set cmd.ActiveConnection = conn
cmd.CommandText = strSQL
cmd.CommandType = 1 ' adCmdText
cmd.Parameters.Append cmd.CreateParameter("@p1", 202, 1, 32, sPart) ' adVarWChar
cmd.Parameters.Append cmd.CreateParameter("@p2", 3, 1, 4, iQty) ' adInteger
cmd.Parameters.Append cmd.CreateParameter("@p3", 202, 1, 16, sOp)
cmd.Parameters.Append cmd.CreateParameter("@p4", 202, 1, 32, sStation)
cmd.Execute , , 128 ' adExecuteNoRecords
If Err.Number <> 0 Then
HMIRuntime.Trace "DB_TransferTrigger: INSERT failed: " & Err.Number & " " & Err.Description & vbCrLf
oStatusTag.Write -1
Else
oStatusTag.Write 0
End If
' --- Tear down --------------------------------------------------
Set cmd = Nothing
conn.Close
Set conn = Nothing
On Error Goto 0
' --- Re-arm the transfer tag ------------------------------------
HMIRuntime.Tags("DB_TransferTrigger").Write False
Two patterns are shown side-by-side above. The first uses a parameterised ADODB.Command object, which is the correct, SQL-injection-safe approach for production. If you want a shorter, less object-heavy variant, replace the cmd.Execute block with a string-concatenated statement and conn.Execute:
strSQL = "INSERT INTO dbo.ProductionLog (PartNumber, Quantity, Operator, StationName) " & _
"VALUES ('" & sPartEsc & "', " & iQty & _
", '" & Replace(sOp, "'", "''") & "', '" & sStation & "')"
conn.Execute strSQL, , 128 ' adExecuteNoRecords
Step 5: Wire the Button Click to Set the Transfer Tag
The transfer tag is set from the operator button. On the same button's Mouse Click event in the Graphics Designer, enter:
HMIRuntime.Tags("DB_TransferTrigger").Write True
That single line is the entire button logic. The action configured in Step 4 will fire within one WinCC scheduling tick (default 250 ms) and run the SQL insert asynchronously relative to the screen refresh, so the button click returns immediately to the operator.
Step 6: Configure the Action Trigger Properly
Open the WinCC Explorer, right-click Global Actions and create a new VBS action named TransferTag_SQLInsert. In the trigger dialog:
- Click Add -> Tag -> OnTagChange.
- Select the internal tag
DB_TransferTrigger. - Set the action's update cycle to Upon change; do not tie it to a 250 ms timer, or the action will also fire on the falling edge (TRUE -> FALSE) when the script resets the tag, doubling the insert.
- Confirm the action is enabled in the startup list of the WinCC runtime computer.
Step 7: Handle the Status and Error Path
Use the DB_TransferStatus tag to drive visible feedback on the screen. Recommended mapping:
| DB_TransferStatus | Meaning | HMI indication |
|---|---|---|
| 0 | Idle, last insert succeeded | Green "DB OK" indicator |
| 1 | Insert in progress | Amber "Writing..." indicator, button disabled |
| -1 | Last insert failed | Red "DB Error" indicator, alarm raised |
You can also raise a WinCC alarm from the failure branch. Inside the On Error block use HMIRuntime.Alarm methods or write a Boolean tag that is wired to an alarm in the Alarm Logging editor. The simplest path is to set a Boolean tag DB_TransferError to TRUE; the alarm system handles the rest.
Step 8: Multi-Row Batch Insertion
If the operator can press the transfer button faster than the SQL round-trip (for example, on a manual rework station with 0.5 s button presses), you should batch. A common pattern is to have the script append values to a CSV file in a local path, and a separate 5-second scheduled action bulk-inserts the file. Inside the per-event action, instead of conn.Execute, write to a queue:
' --- Queue the row to a local staging file ---
Dim fso, ts
Set fso = CreateObject("Scripting.FileSystemObject")
Set ts = fso.OpenTextFile("D:\WinCC\Staging\queue.csv", 8, True) ' 8 = ForAppending
ts.WriteLine sPartEsc & "," & iQty & "," & sOp & "," & sStation
ts.Close
Set ts = Nothing
Set fso = Nothing
' --- Re-arm immediately ---
HMIRuntime.Tags("DB_TransferTrigger").Write False
Then a second scheduled action runs every 5 s and bulk-inserts queued rows with a single BULK INSERT or a TVP (table-valued parameter) call. This collapses N round-trips into one and lets the operator press the button as fast as they want.
Verification
- Open WinCC -> Tools -> Computer -> Properties -> Startup and confirm Global Script Runtime is enabled.
- Activate the project. Open the APDiag window (Start -> Programs -> Siemens Automation -> WinCC -> Tools -> APDiag). APDiag exposes a live view of internal tag values.
- Press the transfer button. Watch
DB_TransferTriggergo TRUE for one scan, then back to FALSE.DB_TransferStatusshould briefly read 1 and then 0. - Open SQL Server Management Studio on the SQL host and run
SELECT TOP 10 * FROM dbo.ProductionLog ORDER BY LogID DESC;. A new row should appear with the values you just wrote. - Force a failure path: stop the SQL Server service, press the button, and confirm
DB_TransferStatusreads -1, an alarm is raised, and the GSC Diagnostics log contains the WinCC trace line with the ADODB error number.
WinCC 7.x vs TIA Portal WinCC RT Professional
The transfer tag pattern is identical, but the editor paths differ:
| Aspect | WinCC 7.4 / 7.5 SCADA | TIA Portal WinCC RT Professional |
|---|---|---|
| VBScript engine | VBScript 5.8, 32-bit, COM | VBScript 5.8, 32-bit, COM (same) |
| Tag object | HMIRuntime.Tags("...") |
HMIRuntime.Tags("...") |
| Action editor location | Graphics Designer event or Computer -> Global Actions | Project tree -> HMI tags -> Events, or scheduled tasks under Runtime |
| Scheduled task | Yes, via Computer -> Global Actions | Yes, via Schedules tab on the HMI device |
| Trace output |
HMIRuntime.Trace to GSC log |
HMIRuntime.Trace to RT log |
| ADODB provider | Same OLE DB providers | Same OLE DB providers; WinCC RT Professional installs the same MDAC stack |
One practical difference: in TIA Portal, the VBScript editor lives under the HMI device -> Screens -> [Screen] -> [Object] -> Events -> [Event name] -> "Edit VBScript". Global actions are configured under "HMI Tags" -> "Connections" -> "Schedules" -> "Add new task" with the VBScript attached.
Troubleshooting Matrix
| Symptom | Likely cause | Fix |
|---|---|---|
| Button does nothing, transfer tag stays TRUE | Action is not in the startup list, or trigger set to timer instead of OnTagChange | Open Computer -> Startup, enable the action. Change trigger to "Upon change" on DB_TransferTrigger
|
| Two rows inserted per button press | Action trigger is a fixed timer; the reset FALSE write fires the next tick | Set trigger to Upon change |
Err.Number = -2147467259 "Automation error" |
SQL Server not reachable, firewall blocking 1433, or named-pipes not enabled | Test with sqlcmd -S SQLHOST\SQLEXPRESS -U wincc_app -P StrongPwd!2024 from the WinCC host. Open TCP 1433 inbound |
Err.Number = -2147217865 "Invalid authorization specification" |
SQL login missing or password wrong | Verify login in SSMS, recreate the SQL user, update the connection string |
Err.Number = -2147217900 "Syntax error in INSERT INTO" |
String contains a single quote that broke the SQL | Switch to the parameterised ADODB.Command approach shown in Step 4 |
| Insert succeeds but HMI freezes for 1-2 s | Connection is opened synchronously inside the action thread | Set conn.ConnectionTimeout = 5 and cmd.CommandTimeout = 10; consider the queue pattern in Step 8 |
| Works on the engineering station, fails on the runtime station | Runtime station uses Local System account, SQL Server expects a domain user | Configure the WinCC service to run as a domain user that has a SQL login |
| No error but no row appears | Action runs in a different process (graphics) than the one you think | Use a global action attached to the computer, not a picture-specific action |
| Duplicate rows under heavy load | Operator pressed button before the script reset the tag | Add a guard: if DB_TransferStatus = 1 and trigger=TRUE, drop the second event or queue it |
Performance and Sizing Notes
A single parameterised INSERT against a local SQL Server over a 1 Gb LAN takes 5-15 ms wall-clock. The ADODB connection open is the dominant cost at 30-80 ms because of the TLS handshake when Encrypt=Yes. If you insert more than 5-10 rows per second sustained, switch to a persistent connection held in an application-level global, or move to the bulk-queue pattern in Step 8. Avoid opening a new ADODB.Connection inside the action body on every trigger: it works, but it will not scale beyond a few events per second and it amplifies the impact of any network blip on SQL Server.
For very high-rate logging (hundreds of rows per second), prefer the WinCC IndustrialDataBridge or WinCC PerformanceIntegrator add-ons, which use the native SQL Server bulk-copy protocol and decouple WinCC from the database entirely. The VBS/ADO path documented here is appropriate for operator-driven, low-frequency, transactional writes, exactly the case the transfer-tag pattern is designed for.
Security and Hardening Checklist
- Use parameterised queries (
ADODB.CommandwithParameters.Append) for every column that contains operator-entered text. - Encrypt the connection string. Do not store
Pwd=...in plain text inside the project. WinCC supports an internal password dialog that masks the field in the editor. - Use a dedicated SQL login with
db_datawriteronly. Do not usesaand do not grantdb_owner. - Force TLS on the SQL connection with
Encrypt=Yes;TrustServerCertificate=No;and a valid CA-issued server certificate on the SQL host. - Audit the SQL Server login for failed login events; a misconfigured WinCC host will produce 4625 events in the Windows Security log on the SQL host.
- Restrict the WinCC service account to the minimum set of Windows privileges: log on as service, write to the project directory, read the ODBC registry keys.
FAQ
Why does my transfer tag stay TRUE and the action never fires?
Most often the global action is not enabled in the Computer -> Startup list, or the trigger was set to a 500 ms timer instead of OnTagChange. Open WinCC Explorer -> Computer -> Startup, confirm the action is enabled, and switch the trigger to Upon change on the DB_TransferTrigger tag.
Can I avoid opening a new ADODB connection on every event?
Yes. Declare a project-wide global ADODB.Connection in the project VBS section and open it once on runtime startup, then reuse it across actions. Add a check in the global OnError handler to reopen the connection if conn.State = 0 (adStateClosed).
How do I write a NULL value to a SQL column from WinCC VBS?
Pass Null directly in the CreateParameter call: cmd.Parameters.Append cmd.CreateParameter("@p3", 202, 1, 16, Null). Do not pass the VBS string "NULL" or an empty string; those are not the same as SQL NULL.
How can I prevent double-inserts if the operator presses the button twice within 250 ms?
Add a guard at the top of the action: If HMIRuntime.Tags("DB_TransferStatus").Value = 1 Then Exit Sub. This drops the second event if the first insert is still in flight. The transfer tag is only reset at the end of the action, so a second press is naturally latched and will fire on the next change event after the first insert completes.
What is the difference between HMIRuntime.Trace and writing to a log file?
HMIRuntime.Trace writes to the GSC Diagnostics log under %ProgramFiles%\Siemens\WinCC\Diagnose\GSCDiagnostics.log (WinCC 7.x) or the equivalent RT log path in TIA Portal. It is the recommended first-line diagnostic because the file is rotated by WinCC and is readable by the WinCC service without extra permissions. Writing to your own file is allowed but requires write access to the target directory and adds I/O jitter to the action thread.