Im trying to set the certificate friendly name during the certificate request/acceptance process. I understand that this a property of the microsoft store rather than the certificate and an wondering what .net/c# technique might be used to set it.
Use X509Certificate2.FriendlyName. However, you must export the certificate as PFX/PKCS#12:
X509Certificate2 certificate = new X509Certificate2(...);
certificate.FriendlyName = "MyName";
File.WriteAllBytes(path, certificate.Export(X509ContentType.Pkcs12));
So here is a commmand line example of how to do this. You need CAPICOM from microsoft which wraps the CryptoAPI.
The friendly name is a property of the cert store rather than the certificate so this code imports a certificate to the cert store and sets the friendly name as it does so.
The code takes two parameters the path to the cert file and the friendly name you wish to set.
Code:-
using System;
using System.Collections.Generic;
using System.Text;
using CAPICOM;
using System.Collections;
using System.Runtime.InteropServices;
namespace CertTool
{
class Program
{
const uint CERT_SYSTEM_STORE_LOCAL_MACHINE = 0x20000;
const int CAPICOM_PROPID_FRIENDLY_NAME = 11;
const int CAPICOM_ENCODE_BINARY = 1;
static private String _currStoreName = "My";
static private String _FriendlyName = "Not Set";
static private String _CertPath = "C:\\test.cer";
static StoreClass _oCurrStore;
static ExtendedPropertyClass _friendlyProp;
static CertificateClass _certificate;
static ExtendedProperties _extendedProp;
static void Main(string[] args)
{
try
{
//Friendly name Argument
if (args.Length > 0)
{
_FriendlyName = args[0];
}
//Certpath argument
if (args.Length > 1)
{
_CertPath = args[1];
}
//Set and open the Store
_oCurrStore = new StoreClass();
_oCurrStore.Open(
CAPICOM_STORE_LOCATION.CAPICOM_LOCAL_MACHINE_STORE,
_currStoreName,
CAPICOM_STORE_OPEN_MODE.CAPICOM_STORE_OPEN_EXISTING_ONLY |
CAPICOM_STORE_OPEN_MODE.CAPICOM_STORE_OPEN_MAXIMUM_ALLOWED);
//Call the import certificate function
importCert();
}
catch(Exception ex){
Console.WriteLine(ex.Message);
Console.WriteLine(args[0]);
}
}
//Function import the certificate to the machine store and sets the friendly name
static bool importCert()
{
try
{
//Create Certificate Object
_certificate = new CertificateClass();
//Load the certificate into the obejct from file
_certificate.Load(_CertPath, "", CAPICOM_KEY_STORAGE_FLAG.CAPICOM_KEY_STORAGE_EXPORTABLE, CAPICOM_KEY_LOCATION.CAPICOM_LOCAL_MACHINE_KEY);
//Create extended property Class for friendly name
_friendlyProp = new ExtendedPropertyClass();
_friendlyProp.PropID = CAPICOM_PROPID.CAPICOM_PROPID_FRIENDLY_NAME;
_friendlyProp.set_Value(CAPICOM_ENCODING_TYPE.CAPICOM_ENCODE_BINARY, _FriendlyName);
//Add extendedProp on cert object
_extendedProp = _certificate.ExtendedProperties();
//Set extendded prop to friendly name object
_extendedProp.Add(_friendlyProp);
_oCurrStore.Add(_certificate);
return true;
}
catch (Exception e)
{
Console.WriteLine(e.Message);
Console.WriteLine(_CertPath);
return true;
}
}
}
}
Ok, found an answer to that here:
Hi,
Please have a look at this to check if it suits your need:
When you run the .net Code in X64 Environment you will get the following error message.
" Failed --Retrieving the COM class factory for component with CLSID ...."
E.g. in CMS Export / Import server side .net code = "ExportSiteContentIncremental(...) Failed --Retrieving the COM class factory for component with CLSID {CA0752B3-021C-4F99-82E3-2C0F19C5E953} failed due to the following error: 80040154."
WORKAROUND:
The possible workaround is modify your project's platform from 'Any CPU' to 'X86' (in Project's Properties, Build/Platform's Target)
ROOTCAUSE
The VSS Interop is a managed assembly using 32-bit Framework and the dll contains a 32-bit COM object. If you run this COM dll in 64 bit environment, you will get the error message.
Related
I am trying to get a test BHO to run in IE 11 - Win 10 in "Enhanced Protection Mode".
All the BHO does is fire an alert, 'hello world'.
I have built the library using 'Any CPU'.
I made sure I wrote to the registry and made it writeable (see full class further down the page):
var registryKey =
Registry.LocalMachine.OpenSubKey(BHO_REGISTRY_KEY_NAME, true);
if (registryKey == null)
registryKey = Registry.LocalMachine.CreateSubKey(
BHO_REGISTRY_KEY_NAME, RegistryKeyPermissionCheck.Default);
I have placed the library in a sub directory of Program Files.
I have run a .bat file that registers the library using x32 and x64 Regasm.
When that didn't work another option I tried was building the library exclusively for x32 and another for x64. Then registering both.
I have spend hours researching this (I have read every post here that looked remotely related) and I can't understand what I am doing wrong.
To be fair the workable solutions I have found here are for Win 8 and Win 7. To be clear I am unable to get this to work on 'Win 10' environment. I CAN get the same code to work on 'Win 7' environment (I don't have Win 8).
I am desperate, any help would be appreciated!!
Install.bat for the Any CPU library:
#echo off
:: Register
SET SYSPATH=%SystemRoot%\Microsoft.NET\Framework\V4.0.30319\RegAsm.exe
%SYSPATH% "%~dp0\IEExtension.dll" /unregister
%SYSPATH% "%~dp0\IEExtension.dll" /codebase
SET SYSPATH=%SystemRoot%\Microsoft.NET\Framework64\V4.0.30319\RegAsm.exe
%SYSPATH% "%~dp0\IEExtension.dll" /unregister
%SYSPATH% "%~dp0\IEExtension.dll" /codebase
pause
The test bho extension code I am using:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
using mshtml;
using Microsoft.Win32;
using SHDocVw;
namespace TestExtension
{
/* define the IObjectWithSite interface which the BHO class will implement.
* The IObjectWithSite interface provides simple objects with a lightweight siting mechanism (lighter than IOleObject).
* Often, an object must communicate directly with a container site that is managing the object.
* Outside of IOleObject::SetClientSite, there is no generic means through which an object becomes aware of its site.
* The IObjectWithSite interface provides a siting mechanism. This interface should only be used when IOleObject is not already in use.
* By using IObjectWithSite, a container can pass the IUnknown pointer of its site to the object through SetSite.
* Callers can also get the latest site passed to SetSite by using GetSite.
*/
[
ComVisible(true),
InterfaceType(ComInterfaceType.InterfaceIsIUnknown),
Guid("FC4801A3-2BA9-11CF-A229-00AA003D7352")
// Never EVER change this UUID!! It allows this BHO to find IE and attach to it
]
public interface IObjectWithSite
{
[PreserveSig]
int SetSite([MarshalAs(UnmanagedType.IUnknown)]object site);
[PreserveSig]
int GetSite(ref Guid guid, out IntPtr ppvSite);
}
/* The BHO site is the COM interface used to establish a communication.
* Define a GUID attribute for your BHO as it will be used later on during
* registration / installation
*/
[
ComVisible(true),
Guid("3295D3BF-AEB3-4F89-96BC-8A83723B1388"),
ClassInterface(ClassInterfaceType.None)
]
public class TestBHO : TestExtension.IObjectWithSite
{
private WebBrowser webBrowser;
public const string BHO_REGISTRY_KEY_NAME =
"Software\\Microsoft\\Windows\\" +
"CurrentVersion\\Explorer\\Browser Helper Objects";
/* The SetSite() method is where the BHO is initialized and where you would perform all
* the tasks that happen only once.
* When you navigate to a URL with Internet Explorer, you should wait for a couple of events to make
* sure the required document has been completely downloaded and then initialized. Only at this point
* can you safely access its content through the exposed object model, if any. This means you need to
* acquire a couple of pointers. The first one is the pointer to IWebBrowser2, the interface that
* renders the WebBrowser object. The second pointer relates to events.
* This module must register as an event listener with the browser in order to receive the
* notification of downloads and document-specific events.
*/
public int SetSite(object site)
{
if (site != null)
{
webBrowser = (WebBrowser)site;
webBrowser.DocumentComplete +=
new DWebBrowserEvents2_DocumentCompleteEventHandler(
this.OnDocumentComplete);
}
else
{
webBrowser.DocumentComplete -=
new DWebBrowserEvents2_DocumentCompleteEventHandler(
this.OnDocumentComplete);
webBrowser = null;
}
return 0;
}
public int GetSite(ref Guid guid, out IntPtr ppvSite)
{
var punk = Marshal.GetIUnknownForObject(webBrowser);
var hr = Marshal.QueryInterface(punk, ref guid, out ppvSite);
Marshal.Release(punk);
return hr;
}
public void OnDocumentComplete(object pDisp, ref object URL)
{
var document = (HTMLDocument)webBrowser.Document;
var head = (IHTMLElement)((IHTMLElementCollection)
document.all.tags("head")).item(null, 0);
var scriptObject =
(IHTMLScriptElement)document.createElement("script");
scriptObject.type = #"text/javascript";
scriptObject.text = "alert('Hello World');"; // <---- JAVASCRIPT INJECTION HAPPENS HERE!
((HTMLHeadElement)head).appendChild((IHTMLDOMNode)scriptObject);
}
/* The Register method simply tells IE which is the GUID of your extension so that it could be loaded.
* The "No Explorer" value simply says that we don't want to be loaded by Windows Explorer.
*/
[ComRegisterFunction]
public static void RegisterBHO(Type type)
{
var registryKey =
Registry.LocalMachine.OpenSubKey(BHO_REGISTRY_KEY_NAME, true);
if (registryKey == null)
registryKey = Registry.LocalMachine.CreateSubKey(
BHO_REGISTRY_KEY_NAME, RegistryKeyPermissionCheck.Default);
var guid = type.GUID.ToString("B");
var ourKey = registryKey?.OpenSubKey(guid, true);
if (ourKey == null)
{
ourKey = registryKey?.CreateSubKey(guid, RegistryKeyPermissionCheck.Default);
}
ourKey?.SetValue("NoExplorer", 1, RegistryValueKind.DWord);
registryKey?.Close();
ourKey?.Close();
}
[ComUnregisterFunction]
public static void UnregisterBHO(Type type)
{
var registryKey =
Registry.LocalMachine.OpenSubKey(BHO_REGISTRY_KEY_NAME, true);
var guid = type.GUID.ToString("B");
if (registryKey != null)
registryKey.DeleteSubKey(guid, false);
}
}
}
I tried to use firewallAPI.dll to add a rule. It works fine for calc.exe (or some other files) as described bellow but fails for msdtc.exe with the following exception:
System.IO.FileNotFoundException: 'The system cannot find the file
specified. (Exception from HRESULT: 0x80070002)'
Example:
static void Main(string[] args)
{
var manager = GetFirewallManager();
if (manager.LocalPolicy.CurrentProfile.FirewallEnabled)
{
var path = #"C:\Windows\System32\calc.exe";
//var path = #"C:\Windows\System32\msdtc.exe"; // System.IO.FileNotFoundException: 'The system cannot find the file specified.
AuthorizeApplication("Test", path, NET_FW_SCOPE_.NET_FW_SCOPE_ALL, NET_FW_IP_VERSION_.NET_FW_IP_VERSION_ANY);
}
}
private const string CLSID_FIREWALL_MANAGER =
"{304CE942-6E39-40D8-943A-B913C40C9CD4}";
private static NetFwTypeLib.INetFwMgr GetFirewallManager()
{
Type objectType = Type.GetTypeFromCLSID(
new Guid(CLSID_FIREWALL_MANAGER));
return Activator.CreateInstance(objectType)
as NetFwTypeLib.INetFwMgr;
}
private const string PROGID_AUTHORIZED_APPLICATION =
"HNetCfg.FwAuthorizedApplication";
public static bool AuthorizeApplication(string title, string applicationPath,
NET_FW_SCOPE_ scope, NET_FW_IP_VERSION_ ipVersion)
{
// Create the type from prog id
Type type = Type.GetTypeFromProgID(PROGID_AUTHORIZED_APPLICATION);
INetFwAuthorizedApplication auth = Activator.CreateInstance(type)
as INetFwAuthorizedApplication;
auth.Name = title;
auth.ProcessImageFileName = applicationPath;
auth.Scope = scope;
auth.IpVersion = ipVersion;
auth.Enabled = true;
INetFwMgr manager = GetFirewallManager();
manager.LocalPolicy.CurrentProfile.AuthorizedApplications.Add(auth);
return true;
}
Note: I checked the folder and see the file is located properly...
Could anybody help to add firewall rule for Distributed Transaction Coordinator? Maybe I should try to add another file to firewall (not msdtc.exe)?
Project > Properties > Build tab, untick the "Prefer 32-bit" checkbox. You don't prefer it, there is no 32-bit version of msdtc.exe.
Why the file system redirector caused the FileNotFoundException is explained well in this MSDN article.
I am trying to implement a solution into my application that mirrors the answer in this post
I have a similar scenario where I have an HttpListener and Grapevine based application running on an Ubuntu server that I need to get working with HTTPS using Mono and I am trying to create and include the relevant keys to allow HTTPS
The problem I am having is the last line of the solution,
key = PrivateKey.CreateFromFile (pvk_file).RSA;
When I try the same Visual Studio shows an error/text highlighted red, 'PrivateKey' does not have a definition for 'CreateFromFile'
Am I using the wrong libraries or is something else the issue with my code itself?
My code, cut down to the relevant method.
using System;
using System.IO;
using System.Security.Cryptography.X509Certificates;
using System.Threading;
using java.security;
public class ConfigureCertificates
{
private readonly string _dirName;
private readonly string _path;
private readonly string _port;
private readonly string _certFile;
public ConfigureCertificates(string port)
{
_dirName = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData);
_path = Path.Combine(_dirName, ".mono");
_path = Path.Combine(_path, "httplistener");
_port = port;
_certFile = Path.Combine(_path, String.Format("{0}.cer", _port));
}
public void SetUpCerts()
{
if (!File.Exists(_certFile))
throw new Exception("Certificate file not found");
string pvkFile = Path.Combine(_path, String.Format("{0}.pvk", _port));
if (!File.Exists(pvkFile))
throw new Exception("Private key not found");
var cert = new X509Certificate2(_certFile);
var key = PrivateKey.CreateFromFile(pvkFile).RSA; // Error occurs here
}
}
You have a naming clash - in other words there is another class called PrivateKey that doesn't have the method you require. A quick Google hunt indicates the correct class is in the Mono.Security.Authenticode namespace. So you will need to reference the full path:
Mono.Security.Authenticode.PrivateKey.CreateFromFile(...)
You may also need to add the Mono.Security package if you don't already have it.
I am trying to write a WMI provider in C# (version 3.5). It'll be a dll that I can install into the GAC and it communicates with a running service via a pipe. Due to a custom updater mechanism on the service, I would like to programatically register and unregister the provider with the WMI repository instead of using installutil.exe.
To isolate my issues and solve them one at a time, I've started with the sample provider from the following link: https://msdn.microsoft.com/en-us/library/cc268228.aspx. I can build it as a DLL, insert it into the GAC (gacutil.exe), and Register it with the WMI repository via InstallUtil.exe. Queries to it via powershell work correctly and I can see its classes under its namespace when I use wbemtest.exe.
However, I've been unsuccessful in my attempts to register the assembly with the WMI repository when I use a separate app I wrote for using InstrumentationManager.RegisterAssembly.
Below is my attempt to programatically install the DLL based on the above sample. I'd appreciate any guidance or directions to samples that show how to implement a provider dll in this manner.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;
using System.IO;
using System.Management.Instrumentation;
using System.Configuration.Install;
//using System.EnterpriseServices.Internal;
namespace wmi_register_tool
{
class Program
{
static void Main(string[] args)
{
bool installing = true;
string asmPath = "";
if (!ParseInput(args, ref installing, ref asmPath))
{
PrintUsage();
return;
}
Assembly myAssem = Assembly.Load(File.ReadAllBytes(asmPath));
Console.WriteLine(myAssem.FullName);
Console.WriteLine("Types contained in " + asmPath + " assembly");
foreach (Type oType in myAssem.GetTypes())
{
Console.WriteLine("\t" + oType.Name);
}
//Publish p = new Publish();
//p.RegisterAssembly(myAssem);
AssemblyInstaller wmi_installer = new AssemblyInstaller(myAssem, null);
Console.WriteLine("--------------> Starting AssemblyInstaller.Install()");
wmi_installer.Install(null);
Console.WriteLine("--------------> Done with AssemblyInstaller.Install()");
Console.WriteLine("--------------> Starting AssemblyInstaller.Commit()");
wmi_installer.Commit(null);
Console.WriteLine("--------------> Done with AssemblyInstaller.Commit()");
try
{
Console.WriteLine("--------------> Starting Instrumentation.RegisterAssembly()");
//Instrumentation.RegisterAssembly(myAssem);
InstrumentationManager.RegisterAssembly(myAssem);
Console.WriteLine("--------------> Done with Instrumentation.RegisterAssembly()");
}
catch (Exception e)
{
Console.WriteLine("Exception Caught: {0}", e);
Console.WriteLine("Exception Message: " + e.Message);
}
}
static bool ParseInput(string[] args, ref bool installing, ref string asmPath)
{
if (args.Length < 2 || args[0].Equals("/h") || args[0].Equals("-h"))
{
return false;
}
asmPath = args[1];
if (args[0].Equals("-i"))
{
installing = true;
return true;
}
else if(args.Equals("-u"))
{
installing = false;
return true;
}
return false;
}
static void PrintUsage()
{
System.Console.WriteLine("wmi_register_tool.exe \n\t-i <filename>\n\t-u <filename>");
}
}
}
I get the following output where it appears to succeed, but then I don't find anything in the WMI repository, so RegisterAssembly must not have successfully inserted it.
C:\wmi_testing>"C:\Program Files (x86)\Microsoft SDKs\Windows\v7.0A\Bin\gacutil.exe" /i WmiServic
eHost.dll
Microsoft (R) .NET Global Assembly Cache Utility. Version 3.5.30729.1
Copyright (c) Microsoft Corporation. All rights reserved.
Assembly successfully added to the cache
C:\wmi_testing>wmi_register_tool.exe -i WmiServiceHost.dll
WmiServiceHost, Version=1.0.0.3, Culture=neutral, PublicKeyToken=3cd934646523c30c
Types contained in WmiServiceHost.dll assembly
SERVICE_STATUS_PROCESS
SCM_ACCESS
SERVICE_ACCESS
SC_STATUS_TYPE
ServiceHandle
SCM
MyInstall
Win32_Process
WIN32ServiceHost
<EnumerateServiceHosts>d__0
--------------> Starting AssemblyInstaller.Install()
Installing assembly 'C:\Windows\assembly\GAC_MSIL\WmiServiceHost\1.0.0.3__3cd934646523c30c\WmiServiceHost.dll'.
Affected parameters are:
assemblypath = C:\Windows\assembly\GAC_MSIL\WmiServiceHost\1.0.0.3__3cd934646523c30c\WmiServiceHost.dll
logfile = C:\Windows\assembly\GAC_MSIL\WmiServiceHost\1.0.0.3__3cd934646523c30c\WmiServiceHost.InstallLog
Installing WMI Schema: Started
Registering assembly: WmiServiceHost_SN_3cd934646523c30c_Version_1.0.0.3
Ensuring that namespace exists: root\MicrosoftWmiNet
Ensuring that class exists: root\MicrosoftWmiNet:WMINET_Instrumentation
Ensuring that class exists: CREATING root\MicrosoftWmiNet:WMINET_Instrumentation
Ensuring that class exists: root\MicrosoftWmiNet:WMINET_InstrumentedNamespaces
Ensuring that class exists: CREATING root\MicrosoftWmiNet:WMINET_InstrumentedNamespaces
Ensuring that class exists: root\MicrosoftWmiNet:WMINET_Naming
Ensuring that class exists: CREATING root\MicrosoftWmiNet:WMINET_Naming
Ensuring that namespace exists: root\default
Ensuring that class exists: root\default:WMINET_Instrumentation
Ensuring that class exists: CREATING root\default:WMINET_Instrumentation
Ensuring that class exists: root\default:WMINET_InstrumentedAssembly
Ensuring that class exists: CREATING root\default:WMINET_InstrumentedAssembly
Ensuring that class exists: root\default:MSFT_DecoupledProvider
Ensuring that class exists: CREATING root\default:MSFT_DecoupledProvider
Ensuring that class exists: root\default:WMINET_ManagedAssemblyProvider
Ensuring that class exists: CREATING root\default:WMINET_ManagedAssemblyProvider
Installing WMI Schema: Finished
--------------> Done with AssemblyInstaller.Install()
--------------> Starting AssemblyInstaller.Commit()
See the contents of the log file for the C:\Windows\assembly\GAC_MSIL\WmiServiceHost\1.0.0.3__3cd934646523c30c\WmiServic
eHost.dll assembly's progress.
The file is located at C:\Windows\assembly\GAC_MSIL\WmiServiceHost\1.0.0.3__3cd934646523c30c\WmiServiceHost.InstallLog.
Committing assembly 'C:\Windows\assembly\GAC_MSIL\WmiServiceHost\1.0.0.3__3cd934646523c30c\WmiServiceHost.dll'.
Affected parameters are:
assemblypath = C:\Windows\assembly\GAC_MSIL\WmiServiceHost\1.0.0.3__3cd934646523c30c\WmiServiceHost.dll
logfile = C:\Windows\assembly\GAC_MSIL\WmiServiceHost\1.0.0.3__3cd934646523c30c\WmiServiceHost.InstallLog
logtoconsole =
--------------> Done with AssemblyInstaller.Commit()
--------------> Starting Instrumentation.RegisterAssembly()
--------------> Done with Instrumentation.RegisterAssembly()
C:\wmi_testing>
EDIT: I tried replacing Instrumentation.RegisterAssembly with the following code and again it ran to completion with no exception, but it printed out more more information to the console indicating that things were going smoothly. In the end the results were the same and I couldn't find my namespace in the WMI repository using wbemtest.exe.
string[] installArgs = new String[] {
"//logfile=",
"//LogToConsole=false",
"//ShowCallStack",
myAssem.Location,
};
System.Configuration.Install.
ManagedInstallerClass.InstallHelper(installArgs);
In my attempt to get the RegisterAssembly function to do the work, I missed the fact that I had achieved my goal while searching for potential dependencies. The AssemblyInstaller.Install and AssemblyInstaller.Commit functions were successfully inserting my provider into the WMI repository. I'm not sure whether RegisterAssembly was removing it and/or messing up the entry or if I had just messed up my WMI repository through continual testing, but when I reset my test VM to a clean state and ran the code with some new lines that cause an exception after AssemblyInstaller.Commit and before RegisterAssembly, I noticed that my provider was correctly inserted into the WMI repository and that I could query it through powershell.
Below is the main that successfully installed it into the WMI repository after I had manually placed the DLL into the GAC.
static void Main(string[] args)
{
bool installing = true;
string asmPath = "";
if (!ParseInput(args, ref installing, ref asmPath))
{
PrintUsage();
return;
}
Assembly myAssem = Assembly.Load(File.ReadAllBytes(asmPath));
Console.WriteLine(myAssem.FullName);
AssemblyInstaller wmi_installer = new AssemblyInstaller(myAssem, null);
wmi_installer.Install(null);
wmi_installer.Commit(null);
}
And just for completeness, I'll include another version that uninstalls and checks if the assembly can be installed prior to trying.
static void Main(string[] args)
{
bool installing = true;
bool installable = false;
string asmPath = "";
if (!ParseInput(args, ref installing, ref asmPath))
{
PrintUsage();
return;
}
IDictionary mySavedState = new Hashtable();
Assembly myAssem = Assembly.Load(File.ReadAllBytes(asmPath));
Console.WriteLine(myAssem.FullName);
AssemblyInstaller wmi_installer = new AssemblyInstaller(myAssem, null);
if (installing)
{
try
{
AssemblyInstaller.CheckIfInstallable(asmPath);
installable = true;
}
catch (Exception)
{
installable = false;
}
if (installable)
{
mySavedState.Clear();
try
{
wmi_installer.Install(mySavedState);
wmi_installer.Commit(mySavedState);
}
catch (Exception)
{
wmi_installer.Rollback(mySavedState);
}
}
}
else
{
try
{
wmi_installer.Uninstall(null);
}
catch (Exception e)
{
Console.WriteLine("Uninstall failed due to: " + e.Message);
}
}
}
I have a command handler which basically works like this:
ControlList.Handlers[CommandType.MyCommandComesHere].Handle(data);
Handlers is a Dictionary<CommandType, ICommandHandler> and CommandType is a enum.
Handle by its turn would lead it to this:
using System;
using log4net;
namespace My_Application
{
public class MyCommand : ICommandHandler
{
private static readonly ILog Logger = LogManager.GetLogger(typeof(MyCommand));
public void Handle(Events data)
{
Console.WriteLine("I can load cs files on the fly yay!!");
}
}
}
My question is how can I make so my application would compile and let me use that cs file while its running?
Any simple example of this would be greatly appreciated but not required as long as I can get some pointers as to what I need to look for as I am not even sure what do I need to make this happen.
To put it simple I am currently trying to understand how could I load a cs file into my application that is already compiled and is currently running.
Using CodeDOM, you need to first create a compiler provider. (You might want to set GenerateExecutable to false and GenerateInMemory to true for your purposes.)
var csc = new CSharpCodeProvider();
var parameters = new CompilerParameters(new[] { "mscorlib.dll", "System.Core.dll" }, "foo.exe", true);
parameters.GenerateExecutable = false;
parameters.GenerateInMemory = true;
Then, you can compile the assembly using CompileAssemblyFromSource and get the CompilerResults returned from it. From this returned object, get a reference to the generated assembly, using its CompiledAssembly property.
var results = csc.CompileAssemblyFromSource(parameters, "contents of the .cs file");
var assembly = results.CompiledAssembly;
Then you can use reflection to create instances from that assembly and call methods on them.
var instance = assembly.CreateInstance("MyCommand");
// etc...
Alternatively, if you're only interested in short code snippets, it might be worth it to use Roslyn instead. You need to create a ScriptEngine first.
var engine = new ScriptEngine();
Then you can just Execute strings on it - or Execute<T> if you're confident that the expression in the string returns a type assignable to T.
var myObject = engine.Execute("1+1");
var myInt = engine.Execute<int>("1+1");
It's definitely more immediate, so it's worth looking into if it serves your purpose.
I have looked for different ways to achieve this and found cs script library lightweight and usable. Here is code snippet how I use it. It runs cs code within app domain so it presumes, that the cs script being compiled comes form trusted source.
using CSScriptLibrary;
using csscript;
using System.CodeDom.Compiler;
using System.Reflection;
//Method example - variable script contains cs code
//This is used to compile cs to DLL and save DLL to a defined location
public Assembly GetAssembly(string script, string assemblyFileName)
{
Assembly assembly;
CSScript.CacheEnabled = true;
try
{
bool debugBuild = false;
#if DEBUG
debugBuild = true;
#endif
if (assemblyFileName == null)
assembly = CSScript.LoadCode(script, null);
else
assembly = CSScript.LoadCode(script, assemblyFileName, debugBuild, null);
return assembly;
}
catch (CompilerException e)
{
//Handle compiler exceptions
}
}
/// <summary>
/// Runs the code either form script text or precompiled DLL
/// </summary>
public void Run(string script)
{
try
{
string tmpPath = GetPathToDLLs(); //Path, where you store precompiled DLLs
string assemblyFileName;
Assembly assembly = null;
if (Directory.Exists(tmpPath))
{
assemblyFileName = Path.Combine(tmpPath, GetExamScriptFileName(exam));
if (File.Exists(assemblyFileName))
{
try
{
assembly = Assembly.LoadFrom(assemblyFileName); //Načtení bez kompilace
}
catch (Exception exAssemblyLoad)
{
Tools.LogError(exAssemblyLoad.Message);
assembly = null;
}
}
}
else
assemblyFileName = null;
//If assembly not found, compile it form script string
if (assembly ==null)
assembly = GetAssembly(script, assemblyFileName);
AsmHelper asmHelper = new AsmHelper(assembly);
//This is how I use the compiled assembly - it depends on your actual code
ICalculateScript calcScript = (ICalculateScript)asmHelper.CreateObject(GetExamScriptClassName(exam));
cex = calcScript.Calculate(this, exam);
Debug.Print("***** Calculated {0} ****", exam.ZV.ZkouskaVzorkuID);
}
catch (Exception e)
{
//handle exceptions
}
}