有用的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)
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.
- You should set your new threads ApartmentState to STA.
- You should call Join metod on your new thread for making your real thread wait for it
- 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.
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
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
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;
}
}
}