Hi All,
We're seeing some issues in long standing COM code in our production environments following the application of a recent windows patch. Has anyone seen this and have any potential environmental fixes/mitigations?
When the same variable is passed more than once in a single late-bound COM Automation (IDispatch::Invoke) call to a COM+-hosted component, the caller-side variables are silently set to VT_EMPTY after the call returns — with no error raised. This affects both ByVal String and ByRef Variant parameters. Early-bound vtable calls to the same component are unaffected, which localises the fault to the late-bound dispatch/marshalling path. We believe, speculatively, that the defect may reside in VARIANT/BSTR lifetime management in oleaut32.dll or a closely related component.
The regression has been reproduced on Windows Server 2016 (KB5094122), Windows Server 2019 (KB5094123), and Windows 11 24H2 (KB5094126). It is consistent with two independent community reports (MS Q&A 5928535 "Regression in OLE Automation after KB5094122 on Windows Server 2016" and MS Q&A 5917867 "After installation of KB5094126 the Word integration over COM fails").
While we do believe it is wider in scope than the OLE automation / Office issue documented in the known issues section of these KB articles - does anyone have details of the workaround provided under the article? https://support.microsoft.com/en-gb/topic/june-9-2026-kb5094122-os-build-14393-9234-e1f06a86-1ecc-497e-a024-6cb3471b57dd - Seems baffling that this would be locked behind Support For Business.
We have rolled back the June update on affected servers as an interim measure but we're under substantial pressure from Customers to resolve this and deploying a code change to mitigate the issue isn't feasible.
More information:
## Component Under Test
```vb
Public Function Echo1( _
ByVal A1 As String, ByVal A2 As String, _
ByVal B1 As String, ByVal B2 As String) As String
Echo1 = "[" & A1 & "][" & A2 & "][" & B1 & "][" & B2 & "]"
End Function
Public Function ByRefEcho1( _
ByRef A1 As Variant, ByRef A2 As Variant, _
ByRef B1 As Variant, ByRef B2 As Variant) As String
ByRefEcho1 = "[" & A1 & "][" & A2 & "][" & B1 & "][" & B2 & "]"
End Function
Echo1 mirrors the original report's signature exactly. ByRefEcho1 is a local extension to test whether the defect is specific to ByVal marshalling.
Test Harness
VBScript executed via cscript.exe, each call isolated with per-call error trapping, results written to file. Post-call VarType/Len of caller variables captured to detect VT_EMPTY corruption.
Option Explicit
Dim obj, a, b, a1, b1, result, fso, ts, report
Set obj = CreateObject("Project1_Dll.Class1")
Set fso = CreateObject("Scripting.FileSystemObject")
Set ts = fso.CreateTextFile("C:\ReplaceIssue\test_results.txt", True)
report = "Label" & vbTab & "Result" & vbTab & "ErrNum" & vbTab & "ErrDesc" & vbCrLf
' Test 1: ByVal, new BSTR per param (CORE workaround case)
a = "100" : b = "200"
On Error Resume Next : Err.Clear
result = obj.Echo1(a & "", a & "", b & "", b & "")
report = report & LogResult("Test1_ByVal_NewBSTR", result, Err.Number, Err.Description)
report = report & LogVars("Test1_PostCall", a, b, a, b)
On Error Goto 0
' Test 2: ByRef Variant, new BSTR per param (EXTENSION)
a1 = "100" : b1 = "200"
On Error Resume Next : Err.Clear
result = obj.ByRefEcho1(a1 & "", a1 & "", b1 & "", b1 & "")
report = report & LogResult("Test2_ByRefVariant_NewBSTR_EXTENSION", result, Err.Number, Err.Description)
report = report & LogVars("Test2_PostCall", a1, b1, a1, b1)
On Error Goto 0
' Test 3: ByVal, distinct variables
a = "100" : a1 = "100" : b = "200" : b1 = "200"
On Error Resume Next : Err.Clear
result = obj.Echo1(a, a1, b, b1)
report = report & LogResult("Test3_ByVal_DistinctVars", result, Err.Number, Err.Description)
report = report & LogVars("Test3_PostCall", a, a1, b, b1)
On Error Goto 0
' Test 4: ByRef Variant, distinct variables (EXTENSION)
a = "100" : a1 = "100" : b = "200" : b1 = "200"
On Error Resume Next : Err.Clear
result = obj.ByRefEcho1(a, a1, b, b1)
report = report & LogResult("Test4_ByRefVariant_DistinctVars_EXTENSION", result, Err.Number, Err.Description)
report = report & LogVars("Test4_PostCall", a, a1, b, b1)
On Error Goto 0
' Test 5: ByVal, SAME variable repeated (CORE REPRO — failing case)
a = "100" : b = "200"
On Error Resume Next : Err.Clear
result = obj.Echo1(a, a, b, b)
report = report & LogResult("Test5_ByVal_SameVarRepeated_CORE_REPRO", result, Err.Number, Err.Description)
report = report & LogVars("Test5_PostCall", a, b, a, b)
On Error Goto 0
' Test 6: ByRef Variant, SAME variable repeated (EXTENSION)
a = "100" : b = "200"
On Error Resume Next : Err.Clear
result = obj.ByRefEcho1(a, a, b, b)
report = report & LogResult("Test6_ByRefVariant_SameVarRepeated_EXTENSION", result, Err.Number, Err.Description)
report = report & LogVars("Test6_PostCall", a, b, a, b)
On Error Goto 0
ts.Write report : ts.Close
MsgBox report, vbOKOnly, "Full Test Report"
Function LogResult(label, res, errNum, errDesc)
If errNum <> 0 Then
LogResult = label & vbTab & "FAILED" & vbTab & errNum & vbTab & errDesc & vbCrLf
Else
LogResult = label & vbTab & res & vbTab & "0" & vbTab & vbCrLf
End If
End Function
Function LogVars(label, v1, v2, v3, v4)
LogVars = label & vbTab & _
"VarType1=" & VarType(v1) & " Len1=" & Len(v1) & vbTab & _
"VarType2=" & VarType(v2) & " Len2=" & Len(v2) & vbTab & _
"VarType3=" & VarType(v3) & " Len3=" & Len(v3) & vbTab & _
"VarType4=" & VarType(v4) & " Len4=" & Len(v4) & vbCrLf
End Function
Results
| # |
Call |
Result |
Post-call (all 4 params) |
Verdict |
| 1 |
Echo1(a & "", a & "", b & "", b & "") |
[100][100][200][200] |
VarType=8, Len=3 |
PASS |
| 2 |
ByRefEcho1(a1 & "", …) (ext.) |
[100][100][200][200] |
VarType=8, Len=3 |
PASS |
| 3 |
Echo1(a, a1, b, b1) distinct vars |
[100][100][200][200] |
VarType=8, Len=3 |
PASS |
| 4 |
ByRefEcho1(a, a1, b, b1) (ext.) |
[100][100][200][200] |
VarType=8, Len=3 |
PASS |
| 5 |
Echo1(a, a, b, b) — CORE REPRO |
[][100][][200] |
VarType=0, Len=0 |
FAIL |
| 6 |
ByRefEcho1(a, a, b, b) (ext.) |
[][100][][200] |
VarType=0, Len=0 |
FAIL |
Failure occurs only when the same variable instance is repeated (tests 5, 6). The VT_EMPTY (VarType 0, Len 0) post-call state matches the original report exactly. Test 6 confirms the identical corruption signature under ByRef Variant, indicating the defect is in a marshalling layer common to both calling conventions, not in ByVal copying specifically.
Binding-Path Isolation (key finding)
The same COM+ component, on the same machine and KB, behaves differently depending solely on the client binding path:
| Client binding |
Activation path |
Result |
VB6 form, early-bound (Dim obj As Project1_Dll.Class1, project reference) |
Direct vtable call |
Works — no corruption |
VB6 form, late-bound (Dim obj As Object + CreateObject) via COM+ |
IDispatch::Invoke marshalled through COM+ |
Fails — [][100][][200] |
VBScript (cscript) via COM+ |
IDispatch::Invoke marshalled through COM+ |
Fails — [][100][][200] |
The only variable changed between the working and failing VB6 cases is early-bound vtable vs. late-bound IDispatch::Invoke. This rules out the component code, the client language (VB6 late-binding fails just as VBScript does), and VBScript specifically. It localises the fault to the late-bound dispatch/marshalling path; early-bound vtable calls bypass it and are unaffected.