RESOLUTION
To work around this problem for Web Forms or for Web
Services, you can do the following:
For Web Forms (.aspx)
Use AspCompat="true" to run your page on an STA thread and to
avoid the thread switch when you call the component.
See Q308095 in
the "References" section of this article for more information.
For Web Services (.asmx)
AspCompat is not available for Web Services (.asmx) so you must
take a different approach. If the impersonated account is static, which means
that you specify a userName and a password in the <identity> tag of
Web.config or Machine.config, or if you always programmatically impersonate the
same account, you can put the STA component in a COM+ Server Application and
then set the identity of the application to the impersonated user.
Note When you call an apartment-threaded or single-threaded COM
component from a Web Service, it is already a requirement to put the component
in some type of COM+ Application.
See Q303375 in the "References"
section of this article for more information.
If the impersonated
account is dynamic (<identity impersonate="true">+ IIS Integrated or
Basic authentication), setting the identity in a COM+ server application will
not suffice. In that case, if you control the code for the STA component then
you can call the
CoImpersonateClient function at the beginning of each method in the component that
requires the impersonated identity. If you do not control the code for the STA
component, or if the code for the component is written by a third party, then
you can introduce a wrapper STA component that first calls
CoImpersonateClient and then calls in the third-party component. There are two
approaches you can use:
- If you must use early binding from your clients, you can
create a separate wrapper component for each third-party component. Each
wrapper component needs separate wrapper methods for each method in its
third-party component.
- If you can use late binding from your clients, you can just
create one generic wrapper component. This wrapper component may create any
third-party component by using ProgID, and it would need only one generic
Invoke method to call into the third-party component.
See the
CallByName function in the "References" section of this article for more
information.
The following example shows how you can:
- Create a new STA thread
- Pass data to the new thread
- Cause the new thread to run under the same identity that
the Web service is impersonating
- Wait for the new thread to finish calling the STA
component
- Extract the results from the new thread and return the
results to the client
Note The intent of this sample is only to show how to use your own STA
thread to impersonate and directly access the COM component. In a production
environment, it is best to manage a pool of STA threads instead of creating and
destroying a single thread on each request.
If you use the example that follows, your STA thread is destroyed immediately after you call the COM component, and then you cannot reference any other COM objects that were created in this STA. Therefore, if your COM object returns a reference to a second COM object that is created in the same STA, you receive the following
InvalidComObjectException: COM object that has been separated from its underlying RCW cannot be used.
To make this work you must keep the STA thread alive and then pump messages to invoke the second COM object, which is beyond the scope of this example.
Create a Text File with Limited Permissions
- Create a text file that is named C:\permissions.txt. Enter
some text into the file.
- Right-click Permissions.txt in Microsoft Windows Explorer, and then click Properties.
- On the Security tab, remove all users, and then give full
control to the user who is currently logged on.
Create an STA COM Component to Access the Text File
- Create a new Microsoft Visual Basic 6 ActiveX DLL project
named FSOWrapper.
- Add a new Class Module that is named CMyFileSystemObject,
and then paste in the following code:
Option Explicit
Dim m_fso As FileSystemObject
Private Sub Class_Initialize()
Set m_fso = New FileSystemObject
End Sub
Public Function OpenTextFileUsingDefaults(ByVal FileName As String) As CMyTextStream
Set OpenTextFileUsingDefaults = OpenTextFile(FileName)
End Function
Public Function OpenTextFile( _
ByVal FileName As String, _
Optional ByVal IOMode As IOMode = ForReading, _
Optional ByVal Create As Boolean = False, _
Optional ByVal Format As Tristate = TristateFalse) As CMyTextStream
Dim oStream As TextStream
Set oStream = m_fso.OpenTextFile(FileName, IOMode, Create, Format)
Set OpenTextFile = New CMyTextStream
OpenTextFile.Init oStream
End Function
- Add a new Class Module that is named CMyTextStream, and
then paste in the following code:
Option Explicit
Dim m_oStream As TextStream
Friend Sub Init(oStream As TextStream)
Set m_oStream = oStream
End Sub
Public Function ReadAll() As String
ReadAll = m_oStream.ReadAll
End Function
- Compile FSOWrapper.dll.
Create an ASP.NET WebService to Call the STA COM Component
- Create a new C# ASP.NET WebService project in Microsoft
Visual Studio .NET.
- In the Location field, type
http://localhost/STAWebService
- In the Internet Information Services snap-in for MMC, visit
the STAWebService virtual directory, and then click Properties. Verify that the Integrated Windows authentication is the only
Authentication Method that is selected.
- Add the following under the <system.web> node of the
Web.config file: <identity impersonate="true"/>
- Paste the following code into the Service1.asmx.cs
file:
using System;
using System.Web.Services;
using FSOWrapper;
using System.Threading;
using System.Security.Principal;
using System.Runtime.InteropServices;
using System.Text;
namespace STAWebService
{
[WebService()]
public class Service1 : WebService
{
/// Create an STA thread under the current impersonated identity
/// so that it can access the STA DLL directly without losing the impersonation.
[WebMethod]
public string TestOpenFileFromSTA(string sFile)
{
StringBuilder returnString = new StringBuilder();
try
{
// Start the output...
StartOutput(returnString);
// Create/init our MySTAThread object.
MySTAThread myThread = new MySTAThread(sFile);
// Start up this new STA thread.
myThread.Start();
// Wait for the new thread to finish.
bool bWaitRet = myThread.Event.WaitOne(1000,false);
// Finish the output...
FinishOutput(returnString, bWaitRet, myThread);
}
catch (Exception e)
{
returnString.Append("<Exception_in_original_thread message = \"" + e.Message + "\"/>");
}
return returnString.ToString();
}
private void StartOutput(StringBuilder returnString)
{
returnString.Append("<Results_from_original_thread>");
returnString.Append("<OriginalIdentity value = \"" + WindowsIdentity.GetCurrent().Name + "\"/>");
}
private void FinishOutput(StringBuilder returnString, bool bWaitRet, MySTAThread myThread)
{
returnString.Append("<FinalIdentity value = \"" + WindowsIdentity.GetCurrent().Name + "\"/>");
returnString.Append("<Done_within_timeout value = \"" + bWaitRet + "\"/>");
returnString.Append("</Results_from_original_thread>");
returnString.Append("<Results_from_myThread>");
returnString.Append("<Success value = \"" + myThread.Success + "\"/>");
returnString.Append("<ImpersonatedIdentity value = \"" + myThread.ImpersonatedIdentity + "\"/>");
returnString.Append("<FileContents value = \"" + myThread.FileContents + "\"/>");
returnString.Append("<Exception value = \"" + myThread.Exception + "\"/>");
returnString.Append("</Results_from_myThread>");
}
}
public class MySTAThread
{
[DllImport("advapi32")]
public static extern bool RevertToSelf();
// You can use public members (or properties) for output...
public bool Success;
public string FileContents;
public string Exception;
public string ImpersonatedIdentity;
// Public event so caller can wait.
public AutoResetEvent Event;
// Privates...
Thread theThread;
WindowsIdentity impersonatedIdentity;
string fileName;
public MySTAThread(string fileName)
{
// Init values;
Success = false;
Exception = "There was no error!";
this.fileName = fileName;
// Create Thread and Event.
Event = new AutoResetEvent(false);
theThread = new Thread(new ThreadStart(MySTAThreadStart));
// Intialize to an STA so that there will be no thread switch to the STA COM object.
theThread.ApartmentState = ApartmentState.STA;
}
public void Start()
{
// Hang on to the current (impersonated) Identity.
impersonatedIdentity = System.Security.Principal.WindowsIdentity.GetCurrent();
// This thread is currently impersonating so any thread you start
// will not have permission to impersonate. You must drop the
// current impersonation token so that your new thread can impersonate.
RevertToSelf();
// Start up the new thread.
theThread.Start();
// Return to the original (impersonated) identity.
impersonatedIdentity.Impersonate();
}
void MySTAThreadStart()
{
try
{
// Impersonate using the Token property.
WindowsImpersonationContext ctx = impersonatedIdentity.Impersonate();
// Store current name of the user for verification.
ImpersonatedIdentity = (WindowsIdentity.GetCurrent().Name);
// Access some STA COM object that requires impersonation.
CMyFileSystemObject oMyFSO = new CMyFileSystemObjectClass();
CMyTextStream oMyTxtStream = oMyFSO.OpenTextFileUsingDefaults(fileName);
FileContents = oMyTxtStream.ReadAll();
Success = true;
}
catch(Exception e)
{
Exception = e.Message ;
Success = false;
}
finally
{
// Drop the impersonation token now that you are finished with it.
RevertToSelf();
// Set the Event property so that the creator can stop waiting on this thread.
Event.Set();
}
}
}
}
Run the WebService from Internet Explorer
- Click Debug | Start Without Debugging (CTRL+F5) menu to start WebService in Internet
Explorer.
- Click the OpenFileFromSTA link.
- Type C:\permissions.txt for the
sFile field, and then click Invoke.
On the new page, you may see code similar to the
following:
<?xml version="1.0" encoding="utf-8" ?>
<string xmlns="http://tempuri.org/">
<Results_from_original_thread>
<OriginalIdentity value="MYDOMAIN\myuser" />
<FinalIdentity value="MYDOMAIN\myuser" />
<Done_within_timeout value="True" />
</Results_from_original_thread>
<Results_from_myThread>
<Success value="True" />
<ImpersonatedIdentity value="MYDOMAIN\myuser" />
<FileContents value="this is some stuff in the file." />
<Exception value="There was no error!" />
</Results_from_myThread>
</string>