之前運作一段時間的電子表單系統,最近又擴充了三台新主機。奇怪的是,在三台新主機上,有個撤銷表單的網頁功能一直出問題,會傳回 Access is denied. (Exception from HRESULT: 0x80070005 (E_ACCESSDENIED)) 錯誤。

通常,存取權限錯誤可以用抓鬼一哥Process Monitor輕易抓出來,但這次卻不然。ProcMon裡看不到可疑的Registry或File Access Denied訊息。Trace Code發現這段程式啟動了TransactionScope,嗯,很有可能是啟動DTC時發生錯誤所致。

於是,我寫了一個WinForm程式在問題主機上測試TransactionScope功能。奇了!! WinForm的測試是成功的,但ASP.NET呼叫元件Class裡一段TransactionScope的Code,卻會得到Access is denied的錯誤。

迫不得已,我只好修改元件Class,將這段的StackTrace也Log下來,發現錯誤發生在:

Access is denied. (Exception from HRESULT: 0x80070005 (E_ACCESSDENIED))
   at System.Transactions.Oletx.IDtcProxyShimFactory.ConnectToProxy(String nodeName, Guid resourceManagerIdentifier, IntPtr managedIdentifier, Boolean& nodeNameMatches, UInt32& whereaboutsSize, CoTaskMemHandle& whereaboutsBuffer, IResourceManagerShim& resourceManagerShim)
   at System.Transactions.Oletx.DtcTransactionManager.Initialize()
   at System.Transactions.Oletx.DtcTransactionManager.get_ProxyShimFactory()
   at System.Transactions.TransactionInterop. GetOletxTransactionFromTransmitterPropigationToken(Byte[] propagationToken)
   at System.Transactions.TransactionStatePSPEOperation.PSPEPromote(InternalTransaction tx)
   at System.Transactions.TransactionStateDelegatedBase.EnterState(InternalTransaction tx)
   at System.Transactions.EnlistableStates.Promote(InternalTransaction tx)
   at System.Transactions.Transaction.Promote()
   at System.Transactions.TransactionInterop.ConvertToOletxTransaction(Transaction transaction)
   at System.Transactions.TransactionInterop.GetExportCookie(Transaction transaction, Byte[] whereabouts)
   at System.Data.SqlClient.SqlInternalConnection.EnlistNonNull(Transaction tx)
   at System.Data.SqlClient.SqlInternalConnection.Enlist(Transaction tx)
   at System.Data.SqlClient.SqlInternalConnectionTds.Activate(Transaction transaction)
   at System.Data.ProviderBase.DbConnectionInternal.ActivateConnection(Transaction transaction)
   at System.Data.ProviderBase.DbConnectionPool.GetConnection(DbConnection owningObject)
   at System.Data.ProviderBase.DbConnectionFactory.GetConnection(DbConnection owningConnection)
   at System.Data.ProviderBase.DbConnectionClosed.OpenConnection(DbConnection outerConnection, DbConnectionFactory connectionFactory)
   at System.Data.SqlClient.SqlConnection.Open()

由StackTrace可以確認,問題是出在DTC沒錯。有了明確的Keyword,很快就Google到類似的文章。不少人抱怨這問題發生在安裝Windows 2003 SP2之後,也看到有些人宣稱用一段類似"咒語"的東西可以解決問題。

最後,我在這篇文章中查到詳細的解答(請看該文第8則回應):

這個問題源自於MSDTC對Authenticated Users的權限設定被設成(A;;CR;;;AU),而它的正確設定應為(A;;CCLCSWRPLOCRRC;;;AU)。這些看起來像咒語的東西,叫做ACE Strings。依據這個理論,在執行WinForm時用的是管理者權限帳號,有別於ASP.NET使用Network Service,之所以TransactionScope在WinForm OK,在ASP.NET中會失敗,就有了合理的解釋。而這個ACE是指使用Service的權限,應該也解釋了為什麼Process Monitor無法觀察到此段存取權限不足的記錄。所有真相都"大白"了(酪梨壽司別怕,此事與您家老爺無關!),感覺真好!

按照該文的說明,我下了sc sdshow MSDTC,得到以下結果:

C:\>sc sdshow msdtc
D:(A;;CCLCSWRPLOCRRC;;;S-1-2-0)(A;;CCLCSWRPWPDTLOCRRC;;;SY)(A;;CCDCLCSWRPWPDTLOC
RSDRCWDWO;;;BA)(A;;CCLCSWLOCRRC;;;IU)(A;;CCLCSWLOCRRC;;;SU)(A;;CR;;;AU)(A;;CCLCS
WRPWPDTLOCRRC;;;PU)(A;;CCLCSWRPLORC;;;NS)S:(AU;FA;CCDCLCSWRPWPDTLOCRSDRCWDWO;;;W
D)

薑!薑!薑!薑~~~ 這幾台問題主機MSDTC的ACE果然被設成(A;;CR;;;AU),我將上述結果中的(A;;CR;;;AU)置換成(A;;CCLCSWRPLOCRRC;;;AU),再用sc sdset MSDTC重新設定。(這裡不貼出語法,怕大家直接Copy回去RUN會出事;正確的做法是先用sdshow取得各機器的設定值,更換其中AU的部分再sdset,切忌Copy後直接拿到別台RUN)

改完設定再試一次,發現原來Access is denied的錯誤訊息變了,變成Communication with the underlying transaction manager has failed,這就是一般性的DTC錯誤了,IISRESET一次,銷單功能恢復正常。Case Closed!


Comments

# by 小熊子

sc sdset MSDTC <剛剛sc sdshow MSDTC 的咒語修正後,請小心換行符號及空白> 執行後就會出現 [SC] SetServiceObjectSecurity SUCCESS

Post a comment