my program installs my custom service and register it. Now what I am trying to do is to unregister the service and delete all files after uninstall. I am using Visual Studio and Setup and Deployment and the Installer class, I have overridden a few methods that I am presenting below:
protected override void OnAfterUninstall(IDictionary savedState)
{
base.OnAfterUninstall(savedState);
string directory = "C:\\Program Files (x86)\\MyService\\";
if (System.IO.Directory.Exists(directory))
{
string[] files = System.IO.Directory.GetFiles(directory);
foreach (string file in files)
{
System.IO.File.Delete(file);
}
System.IO.Directory.Delete(directory);
}
}
protected override void OnBeforeUninstall(IDictionary savedState)
{
base.OnBeforeUninstall(savedState);
string path = "-u \"C:\\Program Files (x86)\\MyService\\AA_service.exe\"";
ProcessStartInfo startInfo = new ProcessStartInfo();
startInfo.CreateNoWindow = false;
startInfo.UseShellExecute = false;
startInfo.FileName = "C:\\Program Files (x86)\\MyService\\InstallUtil.exe";
startInfo.WindowStyle = ProcessWindowStyle.Hidden;
startInfo.Arguments = path;
Process.Start(startInfo);
}
It does not unregister service and it does not delete application folder. Can anyone suggest me what am I doing wrong?
//edit
now it is trying to remove files but I am getting access denied error on uninstall. Files I am trying to delete are .exe, .dll and some others
Did you add the custom actions into the MSI? If you don't have a custom action fire for your uninstall event then I'm not sure if these events will be called. Is there any reason why you are using the before and after install events instead of overriding the "uninstall" command?
If you don't call the Install function for the component, then the installer won't call the uninstall function either. You can program a messagebox into the uninstall (or a System.Diagnostics.Debugger.Attach()) if you want to see whether the code is executing or not.
Also as a matter of portability, I highly recommend that you use the Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles) command to find the program files directory. This will port across x64 and x86 as well as any future revisions to the program files directory in the future.
Secondly I would use the Path.Combine function as well to safely merge the folders together.
EDIT:
I think you may be misusing the installation procedure. The custom action of the installer is to "register" the service once it has been installed by the MSI. It is also to unregister the service before it is then deleted by the MSI.
Go get a copy of WIX or use the MSI builder in Visual Studio. Drop your project output for your service into the project, setup the custom actions to call on your service exe and then the MSI will handle the install/uninstall for you. THe custom action will be called and register/unregister your service with the cache.
Be very careful though, if you need to upgrade there is a bug in the behaviour of the service installer, it is not able to succesfully upgrade or downgrade without you wiring the MSI properly to handle all the sequences that can occur.
using system.Threading;
static void Main()
{
string[] arguments = Environment.GetCommandLineArgs();
foreach (string argument in arguments)
{
if (argument.Split('=')[0].ToLower() == "/u")
{
ThreadStart starter = delegate { WorkThreadFunction(argument.Split('=')[1]); };
new Thread(starter).Start();
Thread.Sleep(30000);
Application.Exit();
return;
}
}
}
Related
I created a WPF Desktop Application as well as a Worker Service (all .NET 6.0 Preview 3), packed them in a .MSI Setup File using Microsoft Visual Studio Installer Projects extension, which installs the WPF Application on the machine.
While the application installs and functions correctly, I had to somehow implement the service installation which should run after the WPF Application would be installed. I created a function for that, which runs sc.exe as administrator and installs the service using Process.Start(), which looks like this:
private static void InstallService()
{
const string ServiceName = "SomeService";
var path = Path.GetFullPath(#".\SomeService.exe");
var psi = new ProcessStartInfo
{
FileName = #"C:\Windows\system32\sc.exe",
Arguments = $"create { ServiceName } binPath= { path } start= auto",
Verb = "runas",
UseShellExecute = true,
};
try
{
Process.Start(psi);
}
catch (Exception ex)
{
MessageBox.Show($"Installation has failed: { ex.Message + ex.StackTrace }");
}
}
The problem with this function is that it executes properly when the application is ran in Visual Studio and when it is ran from the 'bin\Release' folder created by Visual Studio. The service is then installed and can be started. When the program, however, is installed using the .MSI package, the service does not install and no MessageBox is displayed, which shows that no exceptions are thrown.
What I have tried:
When the function is executed, a UAC prompt is shown and then the
process starts. I tried running the entire application as
administrator, but that didn't solve the issue.
I also tried copying all the files from the 'bin\Release' directory into the one in which the application is installed and replacing every file with the one from 'bin\Release', so that both directories should be the same, but that also didn't solve the issue.
After the installation function is executed, the service should start with another function for starting it:
private static void RunService()
{
const string ServiceName = "SomeService";
var psi = new ProcessStartInfo
{
FileName = #"C:\Windows\system32\sc.exe",
Arguments = $"start { ServiceName }",
Verb = "runas",
UseShellExecute = true,
};
try
{
Process.Start(psi);
}
catch (Exception ex)
{
MessageBox.Show($"Running was not approved or failed: { ex.Message }");
}
}
This function, however, functions correctly in both cases, although obviously only when the service is previously installed, which cannot be done in the .MSI installed application. As for the use of Process.Start() instead of ServiceController class, the application should not run as administrator by default, and it is not possible with the ServiceController, so I used Process.Start() with Verb = "runas" which runs the process as administrator only showing the UAC prompt when it is needed (starts the service only when it is not already running).
Is there any way to solve this problem and install a Worker Service in a .MSI installed WPF Application?
As I further proceeded to analyze all the possible factors, I finally noticed what was causing the issue in this case.
Generally, the paths generated by Visual Studio don't have any spaces, and because of that they can be written as a command argument without double quotes. In my case, the paths which contained the Project files also didn't have any spaces, which caused the commands without double quotes to be executed normally. The installation path, however, did contain spaces, as it's designed to be more user-friendly, which caused this piece of code to not execute as intended:
// the path parameter in the command will end when there will be a space somewhere in the path
Arguments = $"create { ServiceName } binPath= { path } start= auto"
The path variable only contains the full path, which is not wrapped in double quotes.
To prevent the issue the use of double quotes is necessary, and including the \ symbol notifies the compiler that these double quotes are a part of the string:
// the path parameter is wrapped in double quotes and will be fully read
Arguments = $"create { ServiceName } binPath= \"{ path }\" start= auto"
When the path would be fully written in the code, the missing double quotes are easy to notice. When, however, using string interpolation, it may cause problems, as it did in my case.
I created a simple C# WPF program, which run various setup of different program by clicking on buttons, so it automates some task when I have to configure PCs.
Initially, I used the function Publish of VS, so when I do changes to the code and deploy again the user click on install update and he has the latest version (fantastic), but this application require admin rights because it install features, create folders such as I wrote above, so I add to my project the app.manifest for running my app with the correct rights. I published again the project, but I got an error, I read that <requestedExecutionLevel level="requireAdministrator" uiAccess="false" /> option and ClickOnce can't exists at the same time, I found some workaround but not very functional, below the code:
public MainWindow()
{
InitializeComponent();
AdminRelauncher();
}
private void AdminRelauncher()
{
if (!IsRunAsAdmin())
{
ProcessStartInfo proc = new ProcessStartInfo();
proc.UseShellExecute = true;
proc.WorkingDirectory = Environment.CurrentDirectory;
proc.FileName = Assembly.GetEntryAssembly().CodeBase;
proc.Verb = "runas";
try
{
Process.Start(proc);
Application.Current.Shutdown();
}
catch (Exception ex)
{
MessageBoxResult mess = MessageBox.Show("Program must be run as admin! \n\n" + ex.ToString());
}
}
}
private bool IsRunAsAdmin()
{
try
{
WindowsIdentity id = WindowsIdentity.GetCurrent();
WindowsPrincipal principal = new WindowsPrincipal(id);
return principal.IsInRole(WindowsBuiltInRole.Administrator);
}
catch (Exception)
{
return false;
}
}
After that i create a setup using Microsoft VS Installer Projects, it works well but every time I do changes or fixes to the code I have to deploy the setup -> unistall the old program -> install the new, this is a waste of time.
So there is any way to deploy a sort of installer or something else which check for application update?
Or better run as administrator the application installed with ClickOnce?
It is best you create another app, a Launcher, whose purpose is to check if a local file on your other app have a version number below the one stored at your database/ftp. If it is true, then your Launcher will download from your ftp the new version and overwrite the old one. It is like I work on my current C# projects :P
Of course, you can also make your Launcher the main executable, at the end of the update check, it can run your program, and thats all, your users only have to run the launcher to update and/or run your main program.
I want to automate my Database creation. there are three Databases to create, I have a different powershell script for each DB creation. Now upon this powershell script i have one more layer batch file this batch file will invokes powershell script.
say #"D:\Parent\Sub\InstallDB1.cmd"; will invoke #"D:\Parent\Powerscript1.ps1 like wise two other.
Now i have single batch file say FinalDB.cmd. The batch file FinalDB.cmd. will invoke three command scripts one after the other will internally call powershell script.
So now the calls in `FinalDB.cmd`
call InstallDB1.cmd //comment: which internally calls Powerscript1.ps1.
call InstallDB2.cmd //comment: which internally calls Powerscript2.ps1.
call InstallDB3.cmd //comment: which internally calls Powerscript3.ps1.
I cant avoid the above scenario because of my application design.
If I run the Final script manually by double clicking, the DB creation process happening without any fail.But failing when i use following C# code to invoke the FinalDB.cmd.
public static RunCommand RunCommandInDir(string workingDirectory, string arguments, string executablePath)
{
RunCommand runResults = new RunCommand
{
Output = new StringBuilder(),
Error = new StringBuilder(),
RunException = null
};
try
{
using (Process proc = new Process())
{
proc.StartInfo.FileName = executablePath;
proc.StartInfo.Arguments = arguments;
proc.StartInfo.WorkingDirectory = workingDirectory;
proc.StartInfo.UseShellExecute = false;
proc.StartInfo.RedirectStandardOutput = true;
proc.StartInfo.RedirectStandardError = true;
proc.StartInfo.CreateNoWindow = false;
proc.OutputDataReceived +=
(o, e) => runResults.Output.Append(e.Data).Append(Environment.NewLine);
proc.ErrorDataReceived +=
(o, e) => runResults.Error.Append(e.Data).Append(Environment.NewLine);
proc.Start();
proc.BeginOutputReadLine();
proc.BeginErrorReadLine();
proc.WaitForExit();
runResults.ExitCode = proc.ExitCode;
}
}
catch (Exception e)
{
runResults.RunException = e;
}
return runResults;
}
When i invoke using the above code i am getting the error "Invoke-Sqlcmd' is not recognized as the name of a cmdlet, function, script file, or operable program", which was not happening when i run my FinalDB.cmd file manually.
I have done the my googling it seems none of the suggested approaches are working.
Any help to fix the issue?Why does the error is coming when i use C# code.
thanks
Invoke-SQLCmd is a cmdlet provided either by the snap-in SqlServerCmdletSnapin100 (prior to SQL Server 2012) or module SQLPS (SQL Server 2012+). You need one or the other loaded into PowerShell (for example, at the beginning of your script) before you can call the cmdlet.
Here is the solution i found my self:
It seems some of the components related to sql server is not installed so i followed the following steps.
Step1:
Open Visual studio Command prompt:
If you have Visual Studio installed on your computer: On the taskbar, click Start, click All Programs, click Visual Studio, click
Visual Studio Tools, and then click Visual Studio Command Prompt.
Step2:
Search the dll file Microsoft.SqlServer.Management.PSProvider.dll in your c: drive which I found under
C:\Program Files (x86)\Microsoft SQL Server\100\Tools\PowerShell\Modules\SQLPS
or it may be under different path
Copy following dlls and paste under the location where your visual studio command window is pointing. So that you can run the
installUtil command from your visual studio command window
Microsoft.SqlServer.Management.PSProvider.dll
Microsoft.SqlServer.Management.PSSnapins.dll
Step3:
Run the Following commands from Visual studio Command prompt
installutil Microsoft.SqlServer.Management.PSProvider.dll
installutil Microsoft.SqlServer.Management.PSSnapins.dll
In my case, I had this error when I had a new development machine. Suddently, a powershell script that worked well started failing. It was because PowerShell Extensions for SQL Server 2008 R2 wasn't installed on the new machine.
Simply, you can download and install it from here. http://www.microsoft.com/en-us/download/details.aspx?id=16978#PowerShell
I have created an application in c# that connects to our QB in order to post invoice ans such. I am ready to deploy this to out accountants so they can further test the application. This app works fine on my machine, however, when I build my project and take the exe to one of our accountants computers I get an error about "COM class factory, blah, blah Class not registered"
In the past that error would mean that I need to build the c# with x86 and not x64, but I did and still receive the error. I now believe that they do not have the QBFC12.dll registered on their computer like I do.
Is this the problem, and if so then do I have to manually register the dll or is their another way to get that dll on their machine?
I found the answer finally.
If you installed QBSDK, explore that folder and locate the Tools folder
Under that you should find a Installers folder that has "QBFC12_0Installer.exe"
Install the exe on the client machine.
Try to run your program, if you encounter another error about "QBXML components are not installed, you will need to re-install QuickBooks and follow from step 1 again. This should get your application up and running for the client.
The person that came the closest and set me on the right track was MikeBr59 so I upvoted him, and posted this incase it helps anyone else.
Thanks,
I think you have to copy it manually and register.
There is no other alternative.
Follow the below steps to copy and register it manually.
1.copy "QBFC12.dll" file into following 2 folders :
a.C:\Windows\System32\
b.C:\Windows\SysWOW64\
2.register the "QBFC12.dll" file using following steps :
open Command Prompt
cd C:\Windows\System32
regsvr32 QBFC12.dll
3.while running the project from visual studio force the platform target as 32 bit -> as the project hasbeen developed in 32 bit using following steps:
Right click on Project
Select Properties
Goto Build Options
Change "Platform Target" from "ANY CPU" to "X86"
4.now run the Project.
Per the QuickBooks SDK docs
You can never redistribute the request processor DLL (qbxmlrp.dll or qbxmlrp2.dll). This violates the license agreement and can lead to undesirable user experiences.
NOTE: It is a violation of your qbXML license agreement to redistribute QBFC, RDS, the QBO connector, or the web connector without using either our stand-alone installers or our merge modules.
To solve this problem, I created the following static class:
public static class QbfcInstaller
{
/// <summary>
/// Installs QBFC if not registered
/// </summary>
public static void install()
{
bool installed = CheckIfInstalled();
if(!installed)
{
string path = #"\\192.168.1.4\App\Publish\QBFC15_0Installer.exe";
ProcessStartInfo startInfo = new ProcessStartInfo();
startInfo.CreateNoWindow = false;
//This is required in order for the Process to be able to request Elevation
startInfo.UseShellExecute = true;
startInfo.FileName = path;
startInfo.WindowStyle = ProcessWindowStyle.Normal;
//This is required in order for the Process to request Elevation
startInfo.Verb = "runas";
try
{
MessageBox.Show("In order to work properly the application needs to install the Quickbooks Connector Library.\nPlease DO NOT skip this step or it will cause errors latter."
, "Install QBFC 15.0", MessageBoxButtons.OK, MessageBoxIcon.Information);
using(Process installProcess = Process.Start(startInfo))
{
installProcess.WaitForExit();
}
}
catch(Exception ex)
{
MessageBox.Show("Unable to install QBFC Error: " + ex.Message);
Process.GetCurrentProcess().Kill();
}
}
}
/// <summary>
/// Checks if QBFC is registered
/// </summary>
/// <returns></returns>
static bool CheckIfInstalled()
{
try
{
IQBSessionManager sessionManager = new QBSessionManager();
}
catch(Exception ex)
{
if(ex.HResult == -2147221164)
{
return false;
}
}
return true;
}
}
Then, I modified Program.Main() to call QbfcInstaller.install() before starting the Application:
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
QbfcInstaller.install();
Application.Run(new FrmLogin());
}
This basically checks if QBFC COM is registered by trying to create a QBSessionMenager. If an error occurs, it checks if ex.Hresult is -2147221164, which is the error that throws if it's not registered. If it's not registered, it then starts the QBFC installer from a local shared folder. (You can also download the installer from a web server and then run it.)
I solved the issue of my x64 application not being able to use QBFC12.dll over COM by creating a COM+ application and registering QBFC12.dll as components of that COM+ application in Windows Component Services.
public void runBatchfile(String batchfilename)
{
try
{
ProcessStartInfo processInfo = new ProcessStartInfo(batchfilename);
processInfo.UseShellExecute = false;
Process batchProcess = new Process();
batchProcess.StartInfo = processInfo;
batchProcess.StartInfo.CreateNoWindow = true;
batchProcess.Start();
batchProcess.WaitForExit();
}
catch (Exception r) { }
}
runBatchfile(#"c:\lol.bat");
lol.bat contains these 2 lines
dir c:\ /s /b > c:\filelist.txt
exit
and when I run my code all it does is creating a filelist.txt file, but doesn't actually perform the rest of the command, which does work if I manually insert it into CMD.
Btw I've tried making the extension .cmd and I also tried without the exit command, without any other results.
please help me :)
On my Windows 7 64-bit machine with Visual Studio 2010, running this code straight from the IDE doesn't do anything whatsoever.
On a hunch that it might have something to do with permission to write to the root directory of drive C:, I tried running the .exe directly from an Explorer window with admin rights (right-click, Run as Administrator) and that worked.
I'd say it's a permission problem.
Maybe you could redirect the output to a file located elsewhere?
update:
I changed the batch file so that the dir command gets redirected to my desktop and it runs just fine.
I've had issues with this as well when calling from C#. The workaround that I usually use is to physically allow it to show the window when running the command. Not sure why this makes a difference but it has worked for me in the past.