If you are developing a setup project with visual studio which contains custom action in it, you might have problems with the OLE calls (for ex: Copy to clipboard or paste from clipboard) and showing a dialog (for ex: FolderBrowser, OpenFileDialog, SaveFileDialog etc.). This is simply because of the thread structure of the setup projects.
MSI projects does not expect that your custom actions needs OLE calls. Therefore they are not calling your custom actions with a thread which is apartment state is STA. If you want to make OLE calls, your thread must be in STA apartment state. Therefore if you show a form in your custom action and try to call Clipboard.SetData or Clipboard.GetData in your form, then you get a STA-MTA thread exception. Also your setup project will be locked when you try to show a dialog form in your custom action form.
To solve this problem, you can just show your custom action main form within a thread which’s apartment state is STA. May be you are thinking changing Thread.CurrentThread apartment model to STA before you show your form but this wont worked, because you should set the apartment state of a thread before it started make any progress. This is why we should create a new STA thread and make our progress on it.
So, if you have such a case in your setup project, just try to create a new thread in your custom actions Install, Commit, Rollback or Uninstall method then begin to show your main form with in that
// The exception object which will store (if) exception which is occurred in our STA thread
private Exception _STAThreadException;
// Install method of our installer.
// If you are showing a custom action form in your Uninstall, Rollback or
// Commit methods, you should also override them like this
public override void Install(IDictionary stateSaver)
{
base.Context.LogMessage(DateTime.Now.ToString("dd/MM/yyyy HH:mm:ss")
+ " Beginning my custom action");
_RunInSTAThread(stateSaver);
}
// Method which creates new STA thread
private void _RunInSTAThread(IDictionary stateSaver)
{
Thread staThread = new Thread(new ParameterizedThreadStart(_RunSTAThread));
staThread.SetApartmentState(ApartmentState.STA);
staThread.Start(stateSaver);
// Wait for the new thread to finish its operations
staThread.Join();
// If there is any exception in the new thread pass it to the installer
if (_STAThreadException != null)
throw _STAThreadException;
}
// Method which contains our custom action behavior
private void _RunSTAThread(object stateSaver)
{
try
{
using (MyCustomActionForm form = new MyCustomActionForm())
{
// You can now use Clipboard.SetData, GetData,
// Show open file dialog, folder select dialog etc. in your form
if (form.ShowDialog() != DialogResult.Ok)
{
throw new Exception("Canceled by user");
}
}
}
catch (Exception ex)
{
_STAThreadException = ex;
}
}
There are some key points in this code example.
- You should set your new threads ApartmentState to STA.
- You should call Join method on your new thread for making your real thread wait for it
- You should transfer Exception (if it is occurred) which occurred in your STA thread to your first thread, because if you don’t do that, your setup will thought that everything is ok in your setup procedure so it will continue to other installation tasks.
Have a god day….