how to enable logging for ManagementClass Install method - c#

I'm using the below code to install the MSI in remote machine. Installation is succeeded, but i want to generate log file for installation steps. please help me on enabling logging.
ManagementClass productClass = new ManagementClass(this.m_WorkingNamespace, new ManagementPath("Win32_Product"), new ObjectGetOptions());
try
{
object[] parameters = { msiFilePath, installOptions, allUsers };
UInt32 returnValue = (UInt32)productClass.InvokeMethod("Install", parameters);
if (returnValue > 0)
throw new Exception("Installation failed. error code = " + returnValue);
}

The normal msi call looks like something like this:
msiexec /i "yourmsi.msi" /l*v "C:\log\example.log"
as you can see you can add a custom log path with you callup of you msi. Make sure the folder for the log is existing and the msi can access it during installation.
Explanation of paramenters
/l - create a protokoll
* - Log all information, except for v and x options
v - Verbose output
If you need futher information for msiexec command lines you can run msiexec from your command line and a help window is shown.

Related

How can I change two items in one string via Windows command prompt and C#

Total newbie at C# and Windows command prompt, so please be patient with me.
This is my first time to write code to create an executable designed to alter the registry, as well as, the preferences in Chrome.
A little background first. The engineers at the company that I am contracted to, use an old program called Cadkey, which is used to view files of things that the company has been manufacturing since the 40s.
As many of you probably know, Chrome no longer allows for applets and other type of files to be viewed in the browser for security purposes, but most engineers at this company would rather use Chrome than IE.
As a result, I have been charged with the task of giving them the ability to open the file via an "application link" and some have also referred to this as "custom url protocol" like the following example:
some file
This allows the engineers to click on the file name in the browser, which then opens the file in the program Cadkey.
To accomplish this, I have to register the key within the user's registry, as well as, alter the preference file of Chrome, so that they are not bothered with the little window that alerts them that this file is about to use the Windows command prompt. I had no issue with the later, but my boss wanted the process to be as smooth as possible.
With all of this said, I was able to accomplish this with the following code:
using System;
using System.IO;
using Microsoft.Win32;
using Newtonsoft.Json.Linq;
namespace CadkeyRegAndPrefs
{
class Program
{
static void Main()
{
try
{
RegistryKey hkCurUsr = Registry.CurrentUser.OpenSubKey("Software\\Classes", true);
// Create a subkey named cadkey under HKEY_CURRENT_USER.
RegistryKey cadkey = hkCurUsr.CreateSubKey("cadkey", true);
cadkey.SetValue("", "URL:cadkey Protocol");
cadkey.SetValue("URL Protocol", "");
// Create data for the defltIcn subkey.
RegistryKey cadkeyDefltIcn = cadkey.CreateSubKey("DefaultIcon", true);
cadkeyDefltIcn.SetValue("", "");
cadkeyDefltIcn.SetValue("C:\\CK19\\Ckwin.exe", "-1");
// Create data for the cadkeyShell subkey.
RegistryKey cadkeyShell = cadkey.CreateSubKey("shell", true);
RegistryKey cadkeyShellOpen = cadkeyShell.CreateSubKey("open", true);
// Create data for the cadkeyCommand subkey.
RegistryKey cadkeyCommand = cadkeyShellOpen.CreateSubKey("command", true);
cadkeyCommand.SetValue("", "");
cadkeyCommand.SetValue("", "cmd /V:ON /C \"SET r=%1 & start C:\\CK19\\Ckwin.exe !r:cadkey:=!\"");
// Retrieve path of the current user
string path = System.Environment.ExpandEnvironmentVariables("%userprofile%");
string pathToPrefs = path + "\\AppData\\Local\\Google\\Chrome\\User Data\\Default\\Preferences";
// Have to create a JObject of the json file
JObject jsonObj = JObject.Parse(File.ReadAllText(pathToPrefs));
// Determine if the user has a protocol handler set and append cadkey set to false, otherwise, create node and set cadkey to false
var isExlcudedSchemes = jsonObj.SelectToken("protocol_handler.excluded_schemes");
if (isExlcudedSchemes != null)
{
jsonObj["protocol_handler"]["excluded_schemes"]["cadkey"] = false;
} else {
jsonObj.Add(new JProperty("protocol_handler", new JObject(
new JProperty("excluded_schemes", new JObject(
new JProperty("cadkey", new JObject()))))));
jsonObj["protocol_handler"]["excluded_schemes"]["cadkey"] = false;
}
// set the variable output and write the json content to the preferences file for Chrome
string output = Newtonsoft.Json.JsonConvert.SerializeObject(jsonObj, Newtonsoft.Json.Formatting.Indented);
File.WriteAllText(pathToPrefs, output);
// Let the end user know that the operation was successful
Console.WriteLine("Cadkey registration installed successfully");
Console.WriteLine("\nPress any key to exit");
Console.ReadKey();
}
catch (Exception e)
{
Console.WriteLine("{0} Exception caught.", e.ToString());
}
}
}
}
The engineers have a little bootstrap modal, which gives them the instructions to download the exe, followed by double clicking the exe to install the registry keys and alter the Chrome prefs.
So what's the problem? The code registers the keys to the user's registry, as well as altering the Chrome preferences file, along with informing the user at the end that it was successful.
The problem is that some of the files have a space in the name, which then makes Cadkey prompt the user that the file cannot be found. Mainly because "%20" appears in the place of the space. I guess Cadkey was made at a time when urlencoding was not part of the design.
I have tried to alter the url via the command prompt, as I do with removing the string "cadkey:" from the param being passed to the command prompt:
cmd /V:ON /C \"SET r=%1 & start C:\\CK19\\Ckwin.exe !r:cadkey:=! & !r:%20= !\"
I have tried:
cmd /V:ON /C \"SET r=%1 & start C:\\CK19\\Ckwin.exe !r:cadkey:=! | !r:%20= !\"
I have tried:
cmd /V:ON /C \"SET r=%1 & start C:\\CK19\\Ckwin.exe !r:cadkey:=! & !r:%%20= !\"
I have tried using another var
cmd /V:ON /C \"SET r=%1 & !r:cadkey:=! & SET s=r start C:\\CK19\\Ckwin.exe !s:%20= !\"
While the command is successful in replace the string "cadkey:" - I have yet to replace both strings at the same time. I have tried too many things, but I am a newbie at this, any help would be appreciated.
Thanks in advance
After working with my boss on this last night, we finally found an answer, which is the following:
Change
cadkeyCommand.SetValue("", "cmd /V:ON /C \"SET r=%1 & start C:\\CK19\\Ckwin.exe !r:cadkey:=!\"");
To:
cadkeyCommand.SetValue("", "cmd /V:ON /C \"SET r=%1 & SET s=!r:cadkey:= ! & SET t=!s:%%20= ! & start C:\\CK19\\Ckwin.exe !t!\"");
The result is that the both the string "cadkey:" and the string "%20" are removed, with the string "%20" replaced by a space, which results in the following being passed to cadkey where you see the variable "t"

.Net Core Command Line App | Process.Start() runs on some machines but not others

Background
I am writing a .NET Core Command Line Application as a CLI for my build system. This part of the build system involves generating a NuGet package from a class library. I am using ProcessStartInfo.cs and Process.cs to make a call to nuget.exe to issue the pack command (nuget.exe location is in the system PATH).
NOTE: I cannot use dotnet CLI for the packaging as the class library is not a .NET Core project, so please do not say "Why don't you just use dotnet pack.
Stack
C# .NET Core (My CLI)
C# .NET 4.5 (Class Library)
Thoughtworks GoCD (Build Server)
Windows Server 2016 (Build Server OS)
Windows 10 (Local Machine OS)
Problem
The issue I am facing is that on my personal machine, when I run my build system CLI, everything works perfectly fine; however, when my build server runs it, the process appears to attempt to start, but then exits without throwing an exception or returning any sort of exit code. Both accounts (Me on my local machine and the account the build server runs under) are local admin accounts.
Code
Cmd.cs (Executes process)
public static class Cmd
{
public static int Execute(string filename, string arguments)
{
var startInfo = new ProcessStartInfo
{
CreateNoWindow = true,
FileName = filename,
Arguments = arguments,
};
using (var process = new Process { StartInfo = startInfo })
{
try
{
process.Start();
process.WaitForExit(30000);
return process.ExitCode;
}
catch (Exception exception)
{
if (!process.HasExited)
{
process.Kill();
}
Console.WriteLine($"Cmd could not execute command {filename} {arguments}:\n{exception.Message}");
return (int)ExitCode.Exception;
}
}
}
}
Package.cs (uses Cmd.cs)
// Some code before this
// The below three variables are generated/provided and are made up here for show
var version = "1.0.0";
var package = $"MyLib.{version}";
var projectPath = "C:\Some\Made\Up\Path";
///////////
// Real code
var tempPath = $"{Path.Combine(Path.GetTempPath(), "NugetPackages")}";
var packagePath = Path.Combine(tempPath, package);
if (!Directory.Exists(tempPath))
{
try
{
Directory.CreateDirectory(tempPath);
}
catch (Exception exception)
{
Console.WriteLine($"Could not create directory in user temp file: {exception.Message}");
return (int) ExitCode.FilePermission;
}
}
var filename = "nuget.exe";
var arguments = $"pack -Version {version} -OutputDirectory {tempPath} -properties Configuration=Release {projectPath}";
var exitCode = Cmd.Execute(filename, arguments);
if (exitCode != 0)
{
Console.WriteLine($"Process failed to execute and exited with exit code: {exitCode}");
return exitCode;
}
Console.WriteLine($"{package} built successfully");
// Some code after this
I modified my code to display all window output and it appears that the same version of NuGet.exe behaves differently on Windows 10 Pro vs Windows Server 2016... not sure why, but on Windows 10 Pro I can use the -Version tag to replace the $version$ token in the .nuspec, but in Windows Server 2016, I cannot do this and have to use a different token (e.g. $package$) and use the -properties switch to replace $package$ with my version... lame sauce!
So, instead of:
nuget.exe pack -Version 1.0.0 -OutputDirectory C:\Somewhere -properties Configuration=Release MyLib
I have to use
nuget.exe pack -OutputDirectory C:\Somwhere -properties "Configuration=Release;package=1.0.0" MyLib

Why can't I install my windows service - Specified service already exists

I wish I could put a grenade to my computer at this point. I'm so frustrated because I don't understand why my application won't install using installUtil.
I've just looked through this link now: Windows Service Install Ends in Rollback and unfortunately the kind suggestions on there don't help in my situation, the following error was generated after taking into consideration all of the answers posted by the good people of SO on that link and others.
I have looked for best practices on the web for task parallel processing patterns but there's nothing helpful so far. The latest error I get when attempting to install is as follows:
.exe assembly's progress. The file is located at
E:\xxx\MyService\Service_V2.InstallLog. Installing assembly
'E:\xxx\MyService\Service_V2.exe'. Affected parameters are:
logtoconsole = logfile = E:\xxx\MyService\Service_V2.InstallLog
assemblypath = E:\xxx\MyService\Service_V2.exe Installing service
Service V2... Service Service V2 has been successfully installed.
Creating EventLog source Service V2 in log Application... Installing
service Service V2... Creating EventLog source Service V2 in log
Application...
An exception occurred during the Install phase.
System.ComponentModel.Win32Exception: The specified service already
exists
The Rollback phase of the installation is beginning. See the contents
of the log file for the E:\xxx\MyService\Service_V2 .exe assembly's
progress. The file is located at
E:\xxx\MyService\Service_V2.InstallLog. Rolling back assembly
'E:\xxx\MyService\Service_V2.exe'. Affected parameters are:
logtoconsole = logfile = E:\xxx\MyService\Service_V2.InstallLog
assemblypath = E:\xxx\MyService\Service_V2.exe Restoring event log to
previous state for source Service V2. Restoring event log to previous
state for source Service V2. Service Service V2 is being removed from
the system... Service Service V2 was successfully removed from the
system.
The Rollback phase completed successfully.
The transacted install has completed. The installation failed, and the
rollback has been performed.
There was nothing written to the event log either.
Here is my OnStart() method:
protected override void OnStart(string[] args)
{
var tokenSource = new CancellationTokenSource();
var token = tokenSource.Token;
ErrorLogFileName = "Service_V2Errors" + DateTime.Now.ToString("yyyy-MM-dd") + ".txt";
Service_V2LogFile = "Service_V2Log" + DateTime.Now.ToString("yyyy-MM-dd") + ".log";
ErrorLogPath = ConfigurationManager.AppSettings["Errorpath"].ToString();
CheckBatchRecord = Convert.ToInt32(ConfigurationManager.AppSettings["CheckBatchTime"].ToString());
if (!Directory.Exists(ErrorLogPath))
{
Directory.CreateDirectory(ErrorLogPath);
}
LogMessage("Starting Service " + DateTime.Now.ToString());
_ErrorLog = new StreamWriter(ErrorLogPath + "//" + ErrorLogFileName, true);
_ErrorLog.WriteLine("Error, Location, AdditionalInformation", true);
_ErrorLog.Close();
var t = Task.Run(() => Service_V2Start(), token);
try
{
t.Wait();
}
catch (AggregateException e)
{
LogMessage("Exception messages:");
foreach (var ie in e.InnerExceptions)
LogMessage(ie.GetType().Name + " : " + ie.Message);
LogMessage("\nTask status: " + t.Status);
}
finally
{
tokenSource.Dispose();
}
}
I have also set the compile mode to release for the final install files compiled.
I have done an "sc delete Servie V2" and I also checked the services console and there is no such service listed there.
I have also tried the InstallUtil.exe -u command to uninstall, but I still get this nitwit error. What should I do now?
Make sure your Program.cs file looks something like this:
static class Program
{
static void Main()
{
var service = new YourServiceName();
ServiceBase.Run(service);
}
}
Inside the InitializeComponent() method make sure that the ServiceName property value is the same as the ServiceName in the ProjectInstaller.cs
this.ServiceName = "MyServiceName";//in the YourServiceName partial class
this.myServiceInstaller.ServiceName = "MyServiceName";//in the installer
Make sure you have only one installer.
In the batch files that you created to install and uninstall your service make sure that you are pointing to the correct InstallUtil.exe.
For 64 bit architectures you can use - C:\Windows\Microsoft.NET\Framework64\v4.0.30319\installutil.exe
For 32 bit architectures you can use - C:\Windows\Microsoft.NET\Framework\v4.0.30319\InstallUtil.exe
Sample InstallService.bat file:
"C:\Windows\Microsoft.NET\Framework64\v4.0.30319\installutil.exe" "PathToTheExecutables\MyServiceName.exe"
pause
Sample UninstallService.bat file:
"C:\Windows\Microsoft.NET\Framework64\v4.0.30319\installutil.exe" /u "PathToTheExecutables\MyServiceName.exe"
pause
Make sure you run cmd as Administrator :)

C# WiX CustomAction Session.Log. Where can I find the log?

I am new to the WiX installer.
I am using Session.Log to log some useful data for the process.
session.Log("Begin register the Vdds interface.");
But I am not sure where can find the log. Is there a default path where it logs? Or should I need to specify the path that I need to provide in installer .wxs file?
You will need to run your installer from the command line using msiexec.exe and then include the L command line option to specify where the logs are to be saved.
For example:
msiexec /i app.msi /l*v thelog.txt
For more information about msiexec's parameters see Command-Line Options
Session.Log adds your log to the standard MSI Log. In case you provided the /l*v <LogPath> switch during execution, logs of custom action would be found in LogPath.
In case you used Property MsiLogging with value as vx in installer,
it would generate Standard MSI Log in user temp location (type %temp% in run), with LogName looking like MSI*.LOG, automatically even without the /L*v switch. LogPath could be overridden though with the /L*v switch.
Things you must know:
session.Log does not log when executed by any UI action.
Sometimes the installer cannot generate the MSI* log because of memory leak issues. In this scenario, you could restart the explorer.exe process.
session.Log works like session.Message with Info level:
public void Log(string msg)
{
if (msg == null)
throw new ArgumentNullException("msg");
using (Record record = new Record(0))
{
record.FormatString = msg;
int num = (int) this.Message(InstallMessage.Info, record);
}
}
You can use dirty trick: define a property in the referencing module and set its value in CA to a message You want to log. It seems that WIX logs changes of properties:
<Property Id="WIX_MAGIX_TRICK_PROPERTY" Value="0" />
and in CA:
session["WIX_MAGIX_TRICK_PROPERTY"] = "message to log";
The result should be similar to this:
MSI (c) (78!34) [09:48:13:770]: PROPERTY CHANGE: Modifying WIX_MAGIX_TRICK_PROPERTY property. Its current value is '0'. Its new value: 'message to log'.
Running the msi with the arguments: /L!*vx solved this for me.
E.g.
msiexec /i MyMsi.msi /L!*vx install.log
Now all my calls to Session.Log("..") now show in install.log
I am not sure where session.Log logs messages. However session.Message:
Record record = new Record();
record.FormatString = string.Format("Something has gone right!");
session.Message(InstallMessage.Info, record);
shows up in the log file generated by:
msiexec /i app.msi /l*v thelog.txt

Uninstalling my Office 2010 plugin leaves a null pointer exception

I've been trying to track down why my Office2010 plugin leaves a null pointer exception during uninstall and the 2007 version does not. (Edit: 2007 is at same state as 2010 - FAIL)
To narrow it down I have put in a couple of eventlog traps, meaning if code reaches this point - I should get something in the Eventlog. No such luck. Now either I written the eventlog trap wrong or code doesn't reach that point.
In the CustomSetupActions - ClickOnceInstaller.cs
public void Uninstall(System.Collections.IDictionary savedState)
{
// write something to eventlog
// This is not being fired, the exception doesn't reach here or writing to eventlog fails.
if (!EventLog.SourceExists("OfficePlugin"))
{
EventLog.CreateEventSource("OfficePlugin", "Application");
}
EventLog.WriteEntry
("OfficePlugin"
, string.Format("Uninstalling: (bug hunting)"), EventLogEntryType.Information);
string deploymentLocation = (string)savedState["deploymentLocation"];
if (deploymentLocation != null)
{
string arguments = String.Format(
"/S /U \"{0}\"", deploymentLocation);
ExecuteVSTOInstaller(arguments);
}
}
As for the ExecuteVSTOInstaller(string arguments)
2007 refers to: string subPath = #"Microsoft Shared\VSTO\9.0\VSTOInstaller.exe";
2010 refers to: string subPath = #"Microsoft Shared\VSTO\10.0\VSTOInstaller.exe";
If the first trap had fired, this is where I would have placed the trap afterwards.
--
I have another method that handles the registration db
RegisterOffice2010AddIn.cs
public void UnRegisterAddIn(string applicationName, string addInName)
{
Next line is precisely the same eventlog trap as I just used/shown.
Difference between the two (2007 vs 2010).
private const string UserSettingsLocation =
#"Software\Microsoft\Office\12.0\User Settings";
vs
private const string UserSettingsLocation =
#"Software\Microsoft\Office\14.0\User Settings";
I can't think of any other place that might be interesting to place the trap. I have a CustomInstaller which doesn't do anything besides Dispose(bool disposing) and InitializeComponent()
Development:
Action start 14:21:00: PublishFeatures.
Action ended 14:21:00: PublishFeatures. Return value 1.
Action start 14:21:00: PublishProduct.
Action ended 14:21:00: PublishProduct. Return value 1.
Action start 14:21:00: InstallExecute.
MSI (c) (B8:BC) [14:21:01:013]: Font created. Charset: Req=0, Ret=0, Font: Req=MS Shell Dlg, Ret=MS Shell Dlg
Error 1001. Error 1001. An exception occurred while uninstalling. This exception will be ignored and the uninstall will continue. However, the application might not be fully uninstalled after the uninstall is complete. --> Object reference not set to an instance of an object.
DEBUG: Error 2769: Custom Action _EE8A0D36_BE55_421F_9A55_95470C001D87.uninstall did not close 1 MSIHANDLEs.
The installer has encountered an unexpected error installing this package. This may indicate a problem with this package. The error code is 2769. The arguments are: _EE8A0D36_BE55_421F_9A55_95470C001D87.uninstall, 1,
Action ended 14:21:05: InstallExecute. Return value 3.
Action ended 14:21:06: INSTALL. Return value 3.
Googling the Error 2769 - gives an answer of "[TARGETDIR]\" , but I dont reference TargetDir, I reference deploymentLocation. And Yes I have tried adding the "\" to places I could think of. Including the setup - Registry - HKLM\Software\MS\Office\12.0\ ...etc... \Addins\Excel/Word/Outlook and the Manifest keyvalue. Gave no feedback, good or bad, errors or otherwise. I'm at a loss what else to try to hunt this one down.
I have a guess it is referencing to this, in the VDPROJ:
{
"Name" = "8:UnregisterOutlookAddIn"
"Condition" = "8:"
"Object" = "8:_73E71A44EB72485AB7367745F7D57F49"
"FileType" = "3:1"
"InstallAction" = "3:4"
"Arguments" = "8:"
"EntryPoint" = "8:"
"Sequence" = "3:3"
"Identifier" = "8:_EE8A0D36_BE55_421F_9A55_95470C001D87"
"InstallerClass" = "11:TRUE"
"CustomActionData" = "8:/addinName=\"OUR.Outlook.Outlook2010AddIn\" /application=\"Outlook\""
}
I found it throws two exception - the secondary under CustomSetupActions and UnregisterAddIn and the primary under ClickOnceInstaller and Uninstall. Howcome I mention them as 2ndary and primary. Well it will do the exception in CustomAction and then the killing one in ClickOnce. I've eliminated the one in CustomActions and I now only have to worry about the ClickOnce. From what I can gather the ClickOnce implements the interface specified in the Setup Project (Install, Rollback, Commit, Uninstall). I only have to figure out how it can run amiss the Uninstall method.
Disclaimer: Unless ofcourse I'm mistaken and is barking up the wrong tree.
Change to WiX. It became a workaround as the original is still true.

Categories

Resources