visual studio setup project custom action ole calls and show dialog support

有用的Default Context.Parameters("assemblypath").比如:string assemblyPath = Context.Parameters["assemblypath"];来得到安装后的CustomAction的Dll路径。


在Visual Studio Setup Project,如果需要OLE calls的操作(比如: Copy to clipboard or paste from clipboard),或者显示对话框(比如:FolderBrowser, OpenFileDialog, SaveFileDialog etc)。需要把执行线程设置为:STA;(Win7/XP都适用)。

// MessageBox.Show is not modal in the setup project
solution:  设置MessageBoxOptions.DefaultDesktopOnly           

 if (MessageBox.Show("Would you like to restore the database?", "Restore database", MessageBoxButtons.YesNo, MessageBoxIcon.Question, MessageBoxDefaultButton.Button1, MessageBoxOptions.DefaultDesktopOnly) == DialogResult.Yes)


http://www.ozcandegirmenci.com/post/2010/08/Visual-Studio-Setup-Project-OLE-Call-And-Dialog-Show-Support.aspx


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 appartment 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 metod then begin to show your main form with in that

// The exception object which will store (if) exception which is occured in our sta thread
private Exception _STAThreadException; 

// Install metod 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 behaviour
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.

  1. You should set your new threads ApartmentState to STA.
  2. You should call Join metod on your new thread for making your real thread wait for it
  3. You should transfer Exception (if it is occured) which occured in your STA thread to your first thread, because if you don't do that, your setup will thought that everything okey in your setup procedure so it will continue to other installation tasks. 

//RestoreDB
USE [master]
GO
IF  EXISTS (SELECT name FROM sys.databases WHERE name = N'MachineryData')
    Alter Database [MachineryData]
        SET SINGLE_USER With ROLLBACK IMMEDIATE

RESTORE DATABASE [MachineryData] FROM  DISK = N'RestoreFileName'
 WITH  FILE = 1, 
 MOVE N'MachineryData' TO N'C:\ProgramFile\Microsoft SQL Server\MSSQL10.SQLEXPRESS\MSSQL\DATA\MachineryData.mdf', 
 MOVE N'MachineryData_log' TO N'C:\ProgramFile\Microsoft SQL Server\MSSQL10.SQLEXPRESS\MSSQL\DATA\MachineryData.LDF',
 REPLACE,
 STATS = 10
GO

//Backup DB
USE [master]
GO
IF EXISTS
   (  SELECT name
      FROM   sys.Databases
      WHERE  name = 'MachineryData'
   )
BEGIN
DECLARE @sql nvarchar(300)
SET @sql = 'BACKUP DATABASE MachineryData TO DISK = '''+ BackupFileName + ''''+ ' WITH FORMAT, NAME = '''+ 'Full Backup of Machiner'+''';'
exec sp_executesql @sql
END

USE [master]
GO

// DropDB
USE [master]
GO
IF EXISTS
   (  SELECT name
      FROM   sys.Databases
      WHERE  name = 'MachineryData'
   )
BEGIN
ALTER DATABASE MachineryData 
    SET SINGLE_USER 
    WITH ROLLBACK IMMEDIATE
   
DROP DATABASE MachineryData
END
Alter Database [MachineryData]
    SET MULTI_USER
Go

//Sample Code关于:安装和卸载时需要备份和恢复数据库的操作

using System;
using System.Collections;
using System.ComponentModel;
using System.Configuration.Install;
using System.Data.SqlClient;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Threading;
using System.Windows.Forms;


namespace ServerCustomerActions
{
    [RunInstaller(true)]
    public partial class ServerInstaller : Installer
    {
        public ServerInstaller()
        {
            InitializeComponent();
        }


        private Exception _staThreadException;
        
        public override void Install(IDictionary stateSaver)
        {
            Thread staThread = new Thread(new ParameterizedThreadStart(Install));
            staThread.SetApartmentState(ApartmentState.STA);
            staThread.Start(stateSaver);
            staThread.Join();


            if (_staThreadException != null)
                throw _staThreadException;
        }


        private void Install(object stateSaver)
        {
            try
            {
                bool isRestoreDb = false;
                // Get the command line input and modify the corresponding db script
                string dbName = Context.Parameters["DBNAME"];
                TextReplacementHelper.InsertTextReplacementTable("MachineryData", dbName);
                string dbUser = Context.Parameters["DBUSER"];
                TextReplacementHelper.InsertTextReplacementTable("MachineryUser", dbUser);
                string userPwd = Context.Parameters["USERPWD"];
                TextReplacementHelper.InsertTextReplacementTable("Dnvmachinery1", userPwd);
                string saPwd = Context.Parameters["SAPWD"];
                TextReplacementHelper.InsertTextReplacementTable("NauticusMachinery", saPwd);


                string programFilePath = "Program Files";
                if (8 == IntPtr.Size
                    || (!String.IsNullOrEmpty(Environment.GetEnvironmentVariable("PROCESSOR_ARCHITEW6432"))))
                {
                    programFilePath = "Program Files (x86)";
                }
                TextReplacementHelper.InsertTextReplacementTable("ProgramFile", programFilePath);


                // Run Datebase Script
                if (!TestDBPresents(dbName))
                {

 if (MessageBox.Show("Would you like to restore the database?", "Restore database", MessageBoxButtons.YesNo, MessageBoxIcon.Question, MessageBoxDefaultButton.Button1, MessageBoxOptions.DefaultDesktopOnly) == DialogResult.Yes)

                    {
                        var dlg = new OpenFileDialog { Multiselect = false, Filter = "Backup Files (*.bak)|*.bak|All Files|*.*" };


                        if (dlg.ShowDialog() == DialogResult.OK)
                        {
                            TextReplacementHelper.InsertTextReplacementTable("RestoreFileName", dlg.FileName);
                            RunDBScript("RestoreDb");
                            isRestoreDb = true;
                        }
                    }


                    if (!isRestoreDb)
                    {
                        RunDBScript("CreateSa");
                        RunDBScript("NMWM_dbscript");
                        RunDBScript("NMWM_CalculationIsLocked");
                        RunDBScript("NMWM_GetCalculationCount");
                        RunDBScript("NMWM_GetCalculations");
                        RunDBScript("NMWM_GetStatistics");
                        RunDBScript("NMWM_GetProjectInfo");
                        RunDBScript("NMWM_LockCalculation");
                        RunDBScript("NMWM_Protocols");
                        RunDBScript("sqlcmd", "CreateMachineryUser");
                    }


                    // Restart the SQL Server
                    RestartSQLServer();


                    // Publish Brix process templates to the database
                    PublishWorkflowToDB("ProcessMachineryCalculations.xml");
                    PublishWorkflowToDB("ProcessMachineryCalculationsSimple.xml");
                }
            }
            catch (Exception ex)
            {
                _staThreadException = ex;
            }
        }
        private static bool TestDBPresents(string dbName)
        {
            bool ret = true;


            if (String.IsNullOrEmpty(dbName))
            {
                dbName = "MachineryData";
            }


            string connectionString = "Server=" + @"(local)\SQLEXPRESS" +
                ";Database=" + dbName +
                ";Integrated Security=SSPI;";


            using (SqlConnection connection = new SqlConnection(connectionString))
            {
                try
                {
                    connection.Open();
                }
                catch (SqlException)
                {
                    // It means it's an empty environmnet
                    ret = false;
                }
            }


            return ret;
        }


        private static void RunDBScript(string resourceName)
        {
            RunDBScript("osql", resourceName);
        }


        private static void RunDBScript(string cmdTool, string resourceName)
        {
            DBScriptHelper dbScriptHelper = DBScriptHelper.CreateHelper(cmdTool);
            if (dbScriptHelper != null)
            {
                dbScriptHelper.Run(resourceName);
            }
        }


        // We add sa user and set mixed mode after the installation of sql server.
        // We should restart the sql server to make the configuration active.
        private static void RestartSQLServer()
        {
            System.ServiceProcess.ServiceController[] sc = System.ServiceProcess.ServiceController.GetServices();
            foreach (System.ServiceProcess.ServiceController s in sc)
            {
                if (s.ServiceName.Equals("MSSQL$SQLEXPRESS"))
                {
                    if (s.Status.Equals(System.ServiceProcess.ServiceControllerStatus.Running))
                    {
                        s.Stop();
                        s.WaitForStatus(System.ServiceProcess.ServiceControllerStatus.Stopped);
                    }


                    s.Start();
                    while (s.Status != System.ServiceProcess.ServiceControllerStatus.Running)
                    {
                        Thread.Sleep(5000);
                        s.Refresh();
                    }


                    break;
                }
            }
        }


        private void PublishWorkflowToDB(string workflowXml)
        {
            string installPath = Path.GetDirectoryName(Context.Parameters["assemblypath"]);
            string workflowcmdExePath = Path.Combine(installPath, "WorkflowCmd.exe");
            string workflowXmlPath = Path.Combine(installPath, workflowXml);
            string workflowConfigPath = Path.Combine(installPath, "WorkflowCmd.exe.config");
            if (!File.Exists(workflowcmdExePath) || !File.Exists(workflowXmlPath) || !File.Exists(workflowConfigPath))
                return;


            // Modify the configuration according to the user's input
            {
                string allText = File.ReadAllText(workflowConfigPath);
                allText = TextReplacementHelper.DoTextReplacement(allText);


                FileAttributes attributes = File.GetAttributes(workflowConfigPath);
                if ((attributes & FileAttributes.ReadOnly) == FileAttributes.ReadOnly)
                {
                    attributes = attributes & ~FileAttributes.ReadOnly;
                    File.SetAttributes(workflowConfigPath, attributes);
                }


                File.WriteAllText(workflowConfigPath, allText);
            }


            //CopyTemplateFromFile <filename> [activate] [onlyIfNewer] [deactivateOlder]:
            //Read a template from an XML file and add it to the database.
            //If activate is specified, the template will be active, otherwise it will be inactive.
            //If onlyIfNewer is specified, the template will only be activated if newer than existing versions of the same template.
            //If deactivateOlder is specified, any older versions of the template will be deactivated.
            string publishWorkflowCmdFile = Path.Combine(Path.GetTempPath(), workflowXml + ".cmd");
            StreamWriter swWriter = new StreamWriter(publishWorkflowCmdFile);
            swWriter.WriteLine("\"" + workflowcmdExePath + "\" CopyTemplateFromFile " + "\"" + workflowXmlPath + "\" activate");
            swWriter.Close();


            Process p = new Process
            {
                StartInfo =
                {
                    FileName = publishWorkflowCmdFile,
                    CreateNoWindow = true,
                    WindowStyle = ProcessWindowStyle.Hidden,
                    WorkingDirectory = Path.GetTempPath()
                }
            };
            p.Start();
            p.WaitForExit();
        }


        private void UnInstall(object stateSaver)
        {
            try
            {
                //  Run Datebase Script
                if (TestDBPresents("MachineryData"))
                {
                    if (MessageBox.Show("During uninstall of the Nauticus Machinery server all the data will be deleted. Do you want to backup the database prior to uninstallation?", "Uninstall", MessageBoxButtons.YesNo, MessageBoxIcon.Question, MessageBoxDefaultButton.Button1, MessageBoxOptions.DefaultDesktopOnly) == DialogResult.Yes)
                    {
                        string backupFolder = @"C:\Temp";
                        if (!Directory.Exists(backupFolder))
                        {
                            Directory.CreateDirectory(backupFolder);
                        }
                        string fileName = Path.Combine(backupFolder,DateTime.Now.Date.ToString("d",CultureInfo.InvariantCulture));
                        fileName = "'" + fileName.Replace("/", "_") + "MachineryData.Bak'";
                        TextReplacementHelper.InsertTextReplacementTable("BackupFileName", fileName);
                        RunDBScript("BackupDb");
                    }
                    RunDBScript("DropDb");
                }
            
                // Restart the SQL Server
                RestartSQLServer();
            }
            catch (Exception ex)
            {
                _staThreadException = ex;
            }
        }


        public override void Uninstall(IDictionary stateSaver)
        {
            Thread staThread = new Thread(new ParameterizedThreadStart(UnInstall));
            staThread.SetApartmentState(ApartmentState.STA);
            staThread.Start(stateSaver);
            staThread.Join();


            if (_staThreadException != null)
                throw _staThreadException;
        }
    }
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值