How to set system environment variable in C#? - c#

I'm trying to set a system environment variable in my application, but get an SecurityException. I tested everything I found in google - without success.
Here is my code (note, that I'm administrator of my pc and run VS2012 as admin):
Attempt 1
new EnvironmentPermission(EnvironmentPermissionAccess.Write, "TEST1").Demand();
Environment.SetEnvironmentVariable("TEST1", "MyTest", EnvironmentVariableTarget.Machine);
Attempt 2
new EnvironmentPermission(EnvironmentPermissionAccess.Write, "TEST1").Demand();
using (var envKey = Registry.LocalMachine.OpenSubKey(#"SYSTEM\CurrentControlSet\Control\Session Manager\Environment", true))
{
Contract.Assert(envKey != null, #"HKLM\System\CurrentControlSet\Control\Session Manager\Environment is missing!");
envKey.SetValue("TEST1", "TestValue");
}
Attempt 3
Also I tried to fit out my app with administrator priviliges.
Do you have any other suggestions?

The documentation tells you how to do this.
Calling SetEnvironmentVariable has no effect on the system environment variables. To programmatically add or modify system environment variables, add them to the HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\Session Manager\Environment registry key, then broadcast a WM_SETTINGCHANGE message with lParam set to the string "Environment". This allows applications, such as the shell, to pick up your updates.
So, you need to write to the registry setting that you are already attempting to write to. And then broadcast a WM_SETTINGCHANGE message as detailed above. You will need to be running with elevated rights in order for this to succeed.
Some example code:
using Microsoft.Win32;
using System;
using System.Diagnostics.Contracts;
using System.Runtime.InteropServices;
namespace ConsoleApplication1
{
class Program
{
const int HWND_BROADCAST = 0xffff;
const uint WM_SETTINGCHANGE = 0x001a;
[DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)]
static extern bool SendNotifyMessage(IntPtr hWnd, uint Msg,
UIntPtr wParam, string lParam);
static void Main(string[] args)
{
using (var envKey = Registry.LocalMachine.OpenSubKey(
#"SYSTEM\CurrentControlSet\Control\Session Manager\Environment",
true))
{
Contract.Assert(envKey != null, #"registry key is missing!");
envKey.SetValue("TEST1", "TestValue");
SendNotifyMessage((IntPtr)HWND_BROADCAST, WM_SETTINGCHANGE,
(UIntPtr)0, "Environment");
}
}
}
}
However, whilst this code does work, the .net framework provides functionality to perform the same task much more simply.
Environment.SetEnvironmentVariable("TEST1", "TestValue",
EnvironmentVariableTarget.Machine);
The documentation for the three argument Environment.SetEnvironmentVariable overload says:
If target is EnvironmentVariableTarget.Machine, the environment variable is stored in the HKEY_LOCAL_MACHINE\SYSTEM\ControlSet001\Control\Session Manager\Environment key of the local computer's registry. It is also copied to all instances of File Explorer. The environment variable is then inherited by any new processes that are launched from File Explorer.
If target is User or Machine, other applications are notified of the set operation by a Windows WM_SETTINGCHANGE message.

Related

Access Violation Error only when running user service account

In our existing application we are using a windows service which has a DLL import to call some functions in native assembly. This has been working fine as long as the service where this native function is called is running as Network Service. It also works when the service runs as LocalSystem.
In order to set customized privileges , we have decided to add support for using a Service account. However simply running the service as a newly created windows user account results in Access Violation error during the Native method call.
Similarly, I tried with a domain account which has administrator permissions on the system to run the service, but still it results in the same problem.
I cannot find any documentation on MSDN where specific permission for user account are listed for DLL Import to work. Can anyone tell me what I might be doing wrong ?
C# Code
[DllImport("lmgr11.dll", CharSet = CharSet.Ansi, BestFitMapping = false, ThrowOnUnmappableChar = true)]
public static extern string GetFeaturelist(short jobType, int flag);
C Code
char* WINAPI GetFeaturelist(int type, int flag)
{
char * featureList;
char ** features, ** tempFeatureList;
int counter = 0;
if (!lm_job)
{
return NULL;
}
features = tempFeatureList = lc_feat_list(lm_job,flag,NULL);
//loop to know the number of features
while(*tempFeatureList != NULL)
{
counter++;
tempFeatureList++;
}
featureList = (char*) CoTaskMemAlloc(counter * 50);
strcpy(featureList,"");
while(*features != NULL)
{
strcat(featureList,*features);
strcat(featureList,"$$&&#&&$$");
features++;
}
lc_log(lm_job, featureList);
return featureList;
}
The problem was that "featurelist" variable in the C code was null, and the below code was not enough to handle it.
while(*features != NULL)
Adding another check using the below code, avoids the crash.
features != NULL
As for the error occurring only when not running as "Network Service", the 3rd Party code (lc_feat_list) depended on an user specific registry entry in HKU, whereas the documentation incorrectly refers to a static registry entry under HKLM, which ultimately caused it to return a null value.

Change wallpaper with service

I'm trying to use a service installed to run as a specific user (me in this case) to change the wallpaper.
Here is my Wallpaper class which has a SetWallpaper function:
public sealed class Wallpaper
{
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError=true)]
private static extern Int32 SystemParametersInfo(
UInt32 action, UInt32 uParam, String vParam, UInt32 winIni);
private static readonly UInt32 SPI_SETDESKWALLPAPER = 0x14;
private static readonly UInt32 SPIF_UPDATEINIFILE = 0x01;
private static readonly UInt32 SPIF_SENDWININICHANGE = 0x02;
public static void SetWallpaper(String path)
{
System.IO.Stream s = new System.Net.WebClient().OpenRead(path.ToString());
System.Drawing.Image img = System.Drawing.Image.FromStream(s);
string tempPath = Path.Combine(Path.GetTempPath(), "wallpaper.bmp");
ImgurWallpaperSetter.ImgurWallpaperSetter.log(tempPath);
img.Save(tempPath, System.Drawing.Imaging.ImageFormat.Bmp);
SystemParametersInfo(SPI_SETDESKWALLPAPER, 1, tempPath,
SPIF_UPDATEINIFILE | SPIF_SENDWININICHANGE);
int error = Marshal.GetLastWin32Error();
ImgurWallpaperSetter.ImgurWallpaperSetter.log("Last error: " + error);
}
}
It works perfectly when I run SetWallpaper from a unit test, but it doesn't work at all when I install the service and start it.
Here is the service start code:
protected override void OnStart(string[] args) {
//WallpaperScheduler.ScheduleWallpaperFetch(DateTime.Now.Hour, DateTime.Now.Minute+1);
//Debugger.Launch();
Uri imageUrl = WallpaperRetriever.mostPopularImgurWallpaper();
log(imageUrl.AbsoluteUri);
Wallpaper.SetWallpaper(imageUrl.AbsoluteUri);
}
I've confirmed that it's downloading the image into my temp directory correctly, but it's not setting the wallpaper. It doesn't error out or log anything to the event logs.
Here's my service installed in the local service viewer:
Running it does nothing.
A similar thread I've read
Edit:
Added this code to run on my serviceInstaller_Committed event which should allow the service to interact with the desktop, but I see a huge delay between the service run and the actual switching of the wallpaper:
ConnectionOptions coOptions = new ConnectionOptions();
coOptions.Impersonation = ImpersonationLevel.Impersonate;
ManagementScope mgmtScope = new ManagementScope(#"root\CIMV2", coOptions);
mgmtScope.Connect();
ManagementObject wmiService;
wmiService = new ManagementObject(
"Win32_Service.Name='" + serviceInstaller1.ServiceName + "'"
);
ManagementBaseObject InParam = wmiService.GetMethodParameters("Change");
InParam["DesktopInteract"] = true;
ManagementBaseObject OutParam = wmiService.InvokeMethod("Change", InParam, null);
Edit2:
I've updated my service to log to the system events GetLastError(). Now I am seeing that the service is throwing error 1459 ("This operation requires an interactive window station."). However, this doesn't explain why my wallpaper does eventually switch (I think usually after waking from sleep). Updated Wallpaper class above as well.
Edit3
I've confirmed that after sleeping, the new wallpaper is set. Can anyone explain why this is? Could it be that I need to restart for the Interactive Desktop ability to be set?
Edit4
What I'm doing is feeling pretty hacky. Would it be better if I had the service do nothing but download wallpapers and potentially had another non-service application for changing the wallpaper if new wallpapers have been downloaded and the user is logged in?
Are you aware of Session 0 Isolation? It means that your service is running in a desktop that no user will ever log into, and that restricted environment may very well be affecting your program's behavior.
You say that the code "doesn't error out or log anything to the event logs", but, based on what you have shown, you need to improve the error checking to catch more subtle problems. For example, SystemParametersInfo() returns FALSE when it fails (and a subsequent call to GetLastError() may be very informative!) but your code doesn't check for that result. You can not rely on explicit exceptions alone.
Add this class: http://pastebin.com/ERsnqMEy
Use it like this: http://pastebin.com/RYvvT7bH
Works wonders in using WMI impersonating the logged in user from a windows system service. Best of luck.
In Services, go to Properties, and check "Use Local Account" and something like "Allow Using of Desktop". I'm not sure about names, because my Windows is in a different language, but you should be able to find it.

"No Disk" error using GDAL from C#/.NET

I am using Tamas Szekeres builds of GDAL including the C# bindings in a desktop GIS application using C# and .net 4.0
I am including the entire GDAL distribution in a sub-directory of my executable with the following folder structure:
\Plugins\GDAL
\Plugins\GDAL\gdal
\Plugins\GDAL\gdal-data
\Plugins\GDAL\proj
We are using EPSG:4326, and the software is built using 32-bit target since the GDAL C# API is using p/invoke to the 32-bit libraries (could try 64 bit since Tamas provides these, haven't gotten around to it yet).
When I run my application I get the following error
This error typically happens when software tries to access a device that is no longer attached, such as a removable drive. It is not possible to "catch" this exception because it pops up a system dialog.
After dismissing the dialog using any of the buttons, the software continues to execute as designed.
The error occurs the first time I call the following method
OSGeo.OSR.CoordinateTransformation.TransformPoint(double[] inout);
The strange stuff:
The error occurs on one, and only one computer (so far)
I've run this software in several other computers both 32 and 64 bit without problems
The error does not ocurr on the first run after compiling the GDAL shim library I am using, it only occurrs on each subsequent run
it happens regardless of release, or debug builds
it happens regardless of whether the debugger is attached or not
it happens regardless of whether I turn on or off Gdal.UseExceptions or Osr.UseExceptions();
disabling removable drives causes the bug to disappear. This is not what I consider a real solution as I will not be able to ask a customer to do this.
I have tried the following:
catching the error
changing GDAL directories and environment settings
changing computers and operating systems: this worked
used SysInternals ProcMon to trace what files are being opened with no luck, they all appear to be files that exist
I re-built the computer in question when the hard drive failed, to no avail.
"cleaning" the registry using CCleaner
files in GDAL Directory are unchanged on execution
Assumptions
Error is happening in unmanaged code
During GDAL initialization, some path is referring to a drive on the computer that is no longer attached.
I am also working on the assumption this is limited to a computer configuration error
Configuration
Windows 7 Pro
Intel Core i7 920 # 2,67GHz
12.0 GB RAM
64-bit OS
Drive C: 120 GB SSD with OS, development (Visual Studio 10), etc
Drive D: 1 TB WD 10,000k with data, not being accessed for data.
The Question
I either need a direction to trap the error, or a tool or technique that will allow me to figure out what is causing it. I don't want to release the software with the possibility that some systems will have this behaviour.
I have no experience with this library, but perhaps some fresh eyes might give you a brainwave...
Firstly, WELL WRITTEN QUESTION! Obviously this problem really has you stumped...
Your note about the error not occurring after a rebuild screams out: Does this library generate some kind of state file, in its binary directory, after it runs?
If so, it is possible that it is saving incorrect path information into that 'configuration' file, in a misguided attempt to accelerate its next start-up.
Perhaps scan this directory for changes between a 'fresh build' and 'first run'?
At very least you might find a file you can clean up on shut-down to avoid this alert...
HTH
Maybe you can try this:
Run diskmgmt.msc
Change the driveletter for Disk 2 (right click) if my assumption that Disk 2 is a Removable Disk is true
Run your application
If this removes the error, something in the application is referring to the old driveletter
It could be in the p/invoked libs
Maybe see: http://gcc.gnu.org/bugzilla/show_bug.cgi?id=46501 It talks about gcc somehow compiling a driveletter into a binary
+1 Great question, but It is not possible to "catch"
Its one of these awful solutions that will turn up on DailyWTF in 5 years. But for now it is stored here http://www.pinvoke.net/default.aspx/user32.senddlgitemmessage
using Microsoft.VisualBasic; //this reference is for the Constants.vbNo;
public partial class Form1 : Form
{
[DllImport("user32.dll")]
static extern IntPtr SendDlgItemMessage(IntPtr hDlg, int nIDDlgItem, uint Msg, UIntPtr wParam, IntPtr lParam);
[DllImport("user32.dll", SetLastError = true)]
public static extern IntPtr SetActiveWindow(IntPtr hWnd);
// For Windows Mobile, replace user32.dll with coredll.dll
[DllImport("user32.dll", SetLastError = true)]
static extern IntPtr FindWindow(string lpClassName, string lpWindowName);
// Find window by Caption only. Note you must pass IntPtr.Zero as the first parameter.
[DllImport("user32.dll", EntryPoint = "FindWindow", SetLastError = true)]
static extern IntPtr FindWindowByCaption(IntPtr ZeroOnly, string lpWindowName);
[DllImport("user32.dll", SetLastError = true)]
static extern uint GetDlgItemText(IntPtr hDlg, int nIDDlgItem,[Out] StringBuilder lpString, int nMaxCount);
public void ClickSaveBoxNoButton()
{
//In this example, we've opened a Notepad instance, entered some text, and clicked the 'X' to close Notepad.
//Of course we received the 'Do you want to save...' message, and we left it sitting there. Now on to the code...
//
//Note: this example also uses API calls to FindWindow, GetDlgItemText, and SetActiveWindow.
// You'll have to find those separately.
//Find the dialog box (no need to find a "parent" first)
//classname is #32770 (dialog box), dialog box title is Notepad
IntPtr theDialogBoxHandle; // = null;
string theDialogBoxClassName = "#32770";
string theDialogBoxTitle = "Notepad";
int theDialogItemId = Convert.ToInt32("0xFFFF", 16);
StringBuilder theDialogTextHolder = new StringBuilder(1000);
//hardcoding capacity - represents maximum text length
string theDialogText = string.Empty;
string textToLookFor = "Do you want to save changes to Untitled?";
bool isChangeMessage = false;
IntPtr theNoButtonHandle; // = null;
int theNoButtonItemId = (int)Constants.vbNo;
//actual Item ID = 7
uint theClickMessage = Convert.ToUInt32("0x00F5", 16);
//= BM_CLICK value
uint wParam = 0;
uint lParam = 0;
//Get a dialog box described by the specified info
theDialogBoxHandle = FindWindow(theDialogBoxClassName, theDialogBoxTitle);
//a matching dialog box was found, so continue
if (theDialogBoxHandle != IntPtr.Zero)
{
//then get the text
GetDlgItemText(theDialogBoxHandle, theDialogItemId, theDialogTextHolder, theDialogTextHolder.Capacity);
theDialogText = theDialogTextHolder.ToString();
}
//Make sure it's the right dialog box, based on the text we got.
isChangeMessage = Regex.IsMatch(theDialogText, textToLookFor);
if ((isChangeMessage))
{
//Set the dialog box as the active window
SetActiveWindow(theDialogBoxHandle);
//And, click the No button
SendDlgItemMessage(theDialogBoxHandle, theNoButtonItemId, theClickMessage, (System.UIntPtr)wParam, (System.IntPtr)lParam);
}
}
It turns out there was no way to definitely answer this question.
I ended up "solving" the problem by figuring out that there was some hardware registered on the system that wasn't present. It is still a mystery to me why, after several years, only GDAL managed to provoke this bug.
I will put the inability to catch this exception down to the idiosyncrasies involved with p/invoke and the hardware error thrown at a very low level on the system.
You could add custom error handlers to gdal. This may help:
Link
http://trac.osgeo.org/gdal/ticket/2895

How to get started with developing Internet Explorer extensions?

Does anyone here have experience with/in developing IE extensions that can share their knowledge? This would include code samples, or links to good ones, or documentation on the process, or anything.
I really want to do this, but I'm hitting a giant wall with lousy documentation, lousy code/example code/lack thereof. Any help/resources you could offer would be greatly appreciated.
Specifically, I would like to start with how to get access to/manipulate the DOM from within a IE extension.
EDIT, even more details:
Ideally, I would like to plant a toolbar button that, when clicked, popped a menu up that contains links to external sites. I would also like to access the DOM and plant JavaScript on the page depending on some conditions.
What is the best way to persist information in an IE extension? In Firefox/Chrome/Most modern browsers, you use window.localStorage, but obviously with IE8/IE7, that's not an option. Maybe a SQLite DB or such? It is okay to assume that .NET 4.0 will be installed on a user's computer?
I don't want to use Spice IE as I want to build one that is compatible with IE9 as well. I've added the C++ tag to this question as well, because if it's better to build one in C++, I can do that.
[UPDATE] I'm updating this answer to work with Internet Explorer 11, in Windows 10 x64 with Visual Studio 2017 Community.
The previous version of this answer (for Internet Explorer 8, in Windows 7 x64 and Visual Studio 2010) is at the bottom of this answer.
Creating a Working Internet Explorer 11 Add-on
I am using Visual Studio 2017 Community, C#, .Net Framework 4.6.1, so some of these steps might be slightly different for you.
You need to open Visual Studio as Administrator to build the solution, so that the post-build script can register the BHO (needs registry access).
Start by creating a class library.
I called mine InternetExplorerExtension.
Add these references to the project:
Interop.SHDocVw: COM tab / search for "Microsoft Internet Controls"
Microsoft.mshtml: Assemblies tab / search for "Microsoft.mshtml"
Note: Somehow MSHTML was not registered in my system, even though I could find in in the Add Reference window. This caused an error while building:
Cannot find wrapper assembly for type library "MSHTML"
The fix can be found at http://techninotes.blogspot.com/2016/08/fixing-cannot-find-wrapper-assembly-for.html
Or, you can run this batch script:
"%ProgramFiles(x86)%\Microsoft Visual Studio\2017\Community\Common7\Tools\VsDevCmd.bat"
cd "%ProgramFiles(x86)%\Microsoft Visual Studio\2017\Community\Common7\IDE\PublicAssemblies"
regasm Microsoft.mshtml.dll
gacutil /i Microsoft.mshtml.dll
Create the following files:
IEAddon.cs
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Windows.Forms;
using Microsoft.Win32;
using mshtml;
using SHDocVw;
namespace InternetExplorerExtension
{
[ComVisible(true)]
[ClassInterface(ClassInterfaceType.None)]
[Guid("D40C654D-7C51-4EB3-95B2-1E23905C2A2D")]
[ProgId("MyBHO.WordHighlighter")]
public class WordHighlighterBHO : IObjectWithSite, IOleCommandTarget
{
const string DefaultTextToHighlight = "browser";
IWebBrowser2 browser;
private object site;
#region Highlight Text
void OnDocumentComplete(object pDisp, ref object URL)
{
try
{
// #Eric Stob: Thanks for this hint!
// This was used to prevent this method being executed more than once in IE8... but now it seems to not work anymore.
//if (pDisp != this.site)
// return;
var document2 = browser.Document as IHTMLDocument2;
var document3 = browser.Document as IHTMLDocument3;
var window = document2.parentWindow;
window.execScript(#"function FncAddedByAddon() { alert('Message added by addon.'); }");
Queue<IHTMLDOMNode> queue = new Queue<IHTMLDOMNode>();
foreach (IHTMLDOMNode eachChild in document3.childNodes)
queue.Enqueue(eachChild);
while (queue.Count > 0)
{
// replacing desired text with a highlighted version of it
var domNode = queue.Dequeue();
var textNode = domNode as IHTMLDOMTextNode;
if (textNode != null)
{
if (textNode.data.Contains(TextToHighlight))
{
var newText = textNode.data.Replace(TextToHighlight, "<span style='background-color: yellow; cursor: hand;' onclick='javascript:FncAddedByAddon()' title='Click to open script based alert window.'>" + TextToHighlight + "</span>");
var newNode = document2.createElement("span");
newNode.innerHTML = newText;
domNode.replaceNode((IHTMLDOMNode)newNode);
}
}
else
{
// adding children to collection
var x = (IHTMLDOMChildrenCollection)(domNode.childNodes);
foreach (IHTMLDOMNode eachChild in x)
{
if (eachChild is mshtml.IHTMLScriptElement)
continue;
if (eachChild is mshtml.IHTMLStyleElement)
continue;
queue.Enqueue(eachChild);
}
}
}
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
#endregion
#region Load and Save Data
static string TextToHighlight = DefaultTextToHighlight;
public static string RegData = "Software\\MyIEExtension";
[DllImport("ieframe.dll")]
public static extern int IEGetWriteableHKCU(ref IntPtr phKey);
private static void SaveOptions()
{
// In IE 7,8,9,(desktop)10 tabs run in Protected Mode
// which prohibits writes to HKLM, HKCU.
// Must ask IE for "Writable" registry section pointer
// which will be something like HKU/S-1-7***/Software/AppDataLow/
// In "metro" IE 10 mode, tabs run in "Enhanced Protected Mode"
// where BHOs are not allowed to run, except in edge cases.
// see http://blogs.msdn.com/b/ieinternals/archive/2012/03/23/understanding-ie10-enhanced-protected-mode-network-security-addons-cookies-metro-desktop.aspx
IntPtr phKey = new IntPtr();
var answer = IEGetWriteableHKCU(ref phKey);
RegistryKey writeable_registry = RegistryKey.FromHandle(
new Microsoft.Win32.SafeHandles.SafeRegistryHandle(phKey, true)
);
RegistryKey registryKey = writeable_registry.OpenSubKey(RegData, true);
if (registryKey == null)
registryKey = writeable_registry.CreateSubKey(RegData);
registryKey.SetValue("Data", TextToHighlight);
writeable_registry.Close();
}
private static void LoadOptions()
{
// In IE 7,8,9,(desktop)10 tabs run in Protected Mode
// which prohibits writes to HKLM, HKCU.
// Must ask IE for "Writable" registry section pointer
// which will be something like HKU/S-1-7***/Software/AppDataLow/
// In "metro" IE 10 mode, tabs run in "Enhanced Protected Mode"
// where BHOs are not allowed to run, except in edge cases.
// see http://blogs.msdn.com/b/ieinternals/archive/2012/03/23/understanding-ie10-enhanced-protected-mode-network-security-addons-cookies-metro-desktop.aspx
IntPtr phKey = new IntPtr();
var answer = IEGetWriteableHKCU(ref phKey);
RegistryKey writeable_registry = RegistryKey.FromHandle(
new Microsoft.Win32.SafeHandles.SafeRegistryHandle(phKey, true)
);
RegistryKey registryKey = writeable_registry.OpenSubKey(RegData, true);
if (registryKey == null)
registryKey = writeable_registry.CreateSubKey(RegData);
registryKey.SetValue("Data", TextToHighlight);
if (registryKey == null)
{
TextToHighlight = DefaultTextToHighlight;
}
else
{
TextToHighlight = (string)registryKey.GetValue("Data");
}
writeable_registry.Close();
}
#endregion
[Guid("6D5140C1-7436-11CE-8034-00AA006009FA")]
[InterfaceType(1)]
public interface IServiceProvider
{
int QueryService(ref Guid guidService, ref Guid riid, out IntPtr ppvObject);
}
#region Implementation of IObjectWithSite
int IObjectWithSite.SetSite(object site)
{
this.site = site;
if (site != null)
{
LoadOptions();
var serviceProv = (IServiceProvider)this.site;
var guidIWebBrowserApp = Marshal.GenerateGuidForType(typeof(IWebBrowserApp)); // new Guid("0002DF05-0000-0000-C000-000000000046");
var guidIWebBrowser2 = Marshal.GenerateGuidForType(typeof(IWebBrowser2)); // new Guid("D30C1661-CDAF-11D0-8A3E-00C04FC9E26E");
IntPtr intPtr;
serviceProv.QueryService(ref guidIWebBrowserApp, ref guidIWebBrowser2, out intPtr);
browser = (IWebBrowser2)Marshal.GetObjectForIUnknown(intPtr);
((DWebBrowserEvents2_Event)browser).DocumentComplete +=
new DWebBrowserEvents2_DocumentCompleteEventHandler(this.OnDocumentComplete);
}
else
{
((DWebBrowserEvents2_Event)browser).DocumentComplete -=
new DWebBrowserEvents2_DocumentCompleteEventHandler(this.OnDocumentComplete);
browser = null;
}
return 0;
}
int IObjectWithSite.GetSite(ref Guid guid, out IntPtr ppvSite)
{
IntPtr punk = Marshal.GetIUnknownForObject(browser);
int hr = Marshal.QueryInterface(punk, ref guid, out ppvSite);
Marshal.Release(punk);
return hr;
}
#endregion
#region Implementation of IOleCommandTarget
int IOleCommandTarget.QueryStatus(IntPtr pguidCmdGroup, uint cCmds, ref OLECMD prgCmds, IntPtr pCmdText)
{
return 0;
}
int IOleCommandTarget.Exec(IntPtr pguidCmdGroup, uint nCmdID, uint nCmdexecopt, IntPtr pvaIn, IntPtr pvaOut)
{
try
{
// Accessing the document from the command-bar.
var document = browser.Document as IHTMLDocument2;
var window = document.parentWindow;
var result = window.execScript(#"alert('You will now be allowed to configure the text to highlight...');");
var form = new HighlighterOptionsForm();
form.InputText = TextToHighlight;
if (form.ShowDialog() != DialogResult.Cancel)
{
TextToHighlight = form.InputText;
SaveOptions();
}
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
return 0;
}
#endregion
#region Registering with regasm
public static string RegBHO = "Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Browser Helper Objects";
public static string RegCmd = "Software\\Microsoft\\Internet Explorer\\Extensions";
[ComRegisterFunction]
public static void RegisterBHO(Type type)
{
string guid = type.GUID.ToString("B");
// BHO
{
RegistryKey registryKey = Registry.LocalMachine.OpenSubKey(RegBHO, true);
if (registryKey == null)
registryKey = Registry.LocalMachine.CreateSubKey(RegBHO);
RegistryKey key = registryKey.OpenSubKey(guid);
if (key == null)
key = registryKey.CreateSubKey(guid);
key.SetValue("Alright", 1);
registryKey.Close();
key.Close();
}
// Command
{
RegistryKey registryKey = Registry.LocalMachine.OpenSubKey(RegCmd, true);
if (registryKey == null)
registryKey = Registry.LocalMachine.CreateSubKey(RegCmd);
RegistryKey key = registryKey.OpenSubKey(guid);
if (key == null)
key = registryKey.CreateSubKey(guid);
key.SetValue("ButtonText", "Highlighter options");
key.SetValue("CLSID", "{1FBA04EE-3024-11d2-8F1F-0000F87ABD16}");
key.SetValue("ClsidExtension", guid);
key.SetValue("Icon", "");
key.SetValue("HotIcon", "");
key.SetValue("Default Visible", "Yes");
key.SetValue("MenuText", "&Highlighter options");
key.SetValue("ToolTip", "Highlighter options");
//key.SetValue("KeyPath", "no");
registryKey.Close();
key.Close();
}
}
[ComUnregisterFunction]
public static void UnregisterBHO(Type type)
{
string guid = type.GUID.ToString("B");
// BHO
{
RegistryKey registryKey = Registry.LocalMachine.OpenSubKey(RegBHO, true);
if (registryKey != null)
registryKey.DeleteSubKey(guid, false);
}
// Command
{
RegistryKey registryKey = Registry.LocalMachine.OpenSubKey(RegCmd, true);
if (registryKey != null)
registryKey.DeleteSubKey(guid, false);
}
}
#endregion
}
}
Interop.cs
using System;
using System.Runtime.InteropServices;
namespace InternetExplorerExtension
{
[ComVisible(true)]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
[Guid("FC4801A3-2BA9-11CF-A229-00AA003D7352")]
public interface IObjectWithSite
{
[PreserveSig]
int SetSite([MarshalAs(UnmanagedType.IUnknown)]object site);
[PreserveSig]
int GetSite(ref Guid guid, [MarshalAs(UnmanagedType.IUnknown)]out IntPtr ppvSite);
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
public struct OLECMDTEXT
{
public uint cmdtextf;
public uint cwActual;
public uint cwBuf;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 100)]
public char rgwz;
}
[StructLayout(LayoutKind.Sequential)]
public struct OLECMD
{
public uint cmdID;
public uint cmdf;
}
[ComImport(), ComVisible(true),
Guid("B722BCCB-4E68-101B-A2BC-00AA00404770"),
InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIUnknown)]
public interface IOleCommandTarget
{
[return: MarshalAs(UnmanagedType.I4)]
[PreserveSig]
int QueryStatus(
[In] IntPtr pguidCmdGroup,
[In, MarshalAs(UnmanagedType.U4)] uint cCmds,
[In, Out, MarshalAs(UnmanagedType.Struct)] ref OLECMD prgCmds,
//This parameter must be IntPtr, as it can be null
[In, Out] IntPtr pCmdText);
[return: MarshalAs(UnmanagedType.I4)]
[PreserveSig]
int Exec(
//[In] ref Guid pguidCmdGroup,
//have to be IntPtr, since null values are unacceptable
//and null is used as default group!
[In] IntPtr pguidCmdGroup,
[In, MarshalAs(UnmanagedType.U4)] uint nCmdID,
[In, MarshalAs(UnmanagedType.U4)] uint nCmdexecopt,
[In] IntPtr pvaIn,
[In, Out] IntPtr pvaOut);
}
}
and finally a form, that we will use to configure the options. In this form place a TextBox and an Ok Button. Set the DialogResult of the button to Ok. Place this code in the form code:
using System.Windows.Forms;
namespace InternetExplorerExtension
{
public partial class HighlighterOptionsForm : Form
{
public HighlighterOptionsForm()
{
InitializeComponent();
}
public string InputText
{
get { return this.textBox1.Text; }
set { this.textBox1.Text = value; }
}
}
}
In the project properties, do the following:
Sign the assembly with a strong-key;
In the Debug tab, set Start External Program to C:\Program Files (x86)\Internet Explorer\iexplore.exe
In the Debug tab, set Command Line Arguments to http://msdn.microsoft.com/en-us/library/ms976373.aspx#bho_getintouch
In the Build Events tab, set Post-build events command line to:
"%ProgramFiles(x86)%\Microsoft SDKs\Windows\v10.0A\bin\NETFX 4.6.1 Tools\gacutil.exe" /f /i "$(TargetDir)$(TargetFileName)"
"%windir%\Microsoft.NET\Framework\v4.0.30319\RegAsm.exe" /unregister "$(TargetDir)$(TargetFileName)"
"%windir%\Microsoft.NET\Framework\v4.0.30319\RegAsm.exe" "$(TargetDir)$(TargetFileName)"
Attention: even though my computer is x64, I used the path of the non-x64 gacutil.exe and it worked... the one specific for x64 is at:
C:\Program Files (x86)\Microsoft SDKs\Windows\v10.0A\bin\NETFX 4.6.1 Tools\x64\gacutil.exe
64bit IE Needs 64bit-compiled and 64bit-registered BHO. Though I could only debug using 32bit IE11, the 32bit registered extension also worked by running 64bit IE11.
This answer appears to have some additional info about this: https://stackoverflow.com/a/23004613/195417
If you need to, you can use the 64bit regasm:
%windir%\Microsoft.NET\Framework64\v4.0.30319\RegAsm.exe
How this add-on works
I didn't change the behavior of the add-on... take a look at IE8 section bellow for description.
## Previous Answer for IE8
Man... this has been a lot of work!
I was so curious about how to do this, that I did it myself.
First of all... credit is not all mine. This is a compilation of what I found, on these sites:
CodeProject article, how to make a BHO;
15seconds, but it was not 15 seconds, it took about 7 hours;
Microsoft tutorial, helped me adding the command button.
And this social.msdn topic, that helped me figure out that the assembly must be in the GAC.
This MSDN blog post contains a fully-working example
many other sites, in the discovery process...
And of course, I wanted my answer to have the features you asked:
DOM traversal to find something;
a button that shows a window (in my case to setup)
persist the configuration (I will use registry for that)
and finally execute javascript.
I will describe it step by step, how I managed to do it working with Internet Explorer 8, in Windows 7 x64... note that I could not test in other configurations. Hope you understand =)
Creating a Working Internet Explorer 8 Add-on
I am using Visual Studio 2010, C# 4, .Net Framework 4, so some of these steps might be slightly different for you.
Created a class library. I called mine InternetExplorerExtension.
Add these references to the project:
Interop.SHDocVw
Microsoft.mshtml
Note: These references may be in different places in each computer.
this is what my references section in csproj contains:
<Reference Include="Interop.SHDocVw, Version=1.1.0.0, Culture=neutral, PublicKeyToken=90ba9c70f846762e, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<EmbedInteropTypes>True</EmbedInteropTypes>
<HintPath>C:\Program Files (x86)\Microsoft Visual Studio 9.0\Common7\IDE\PrivateAssemblies\Interop.SHDocVw.dll</HintPath>
</Reference>
<Reference Include="Microsoft.CSharp" />
<Reference Include="Microsoft.mshtml, Version=7.0.3300.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<EmbedInteropTypes>True</EmbedInteropTypes>
</Reference>
<Reference Include="System" />
<Reference Include="System.Data" />
<Reference Include="System.Drawing" />
<Reference Include="System.Windows.Forms" />
<Reference Include="System.Xml" />
Create the files the same way as the updated IE11 files.
IEAddon.cs
You can uncomment the following lines from IE11 version:
...
// #Eric Stob: Thanks for this hint!
// This was used to prevent this method being executed more than once in IE8... but now it seems to not work anymore.
if (pDisp != this.site)
return;
...
Interop.cs
Same as IE11 version.
and finally a form, that we will use to configure the options. In this form place a TextBox and an Ok Button. Set the DialogResult of the button to Ok. The code is the same for IE11 addon.
In the project properties, do the following:
Sign the assembly with a strong-key;
In the Debug tab, set Start External Program to C:\Program Files (x86)\Internet Explorer\iexplore.exe
In the Debug tab, set Command Line Arguments to http://msdn.microsoft.com/en-us/library/ms976373.aspx#bho_getintouch
In the Build Events tab, set Post-build events command line to:
"C:\Program Files (x86)\Microsoft SDKs\Windows\v7.0A\Bin\NETFX 4.0 Tools\x64\gacutil.exe" /f /i "$(TargetDir)$(TargetFileName)"
"C:\Windows\Microsoft.NET\Framework\v4.0.30319\RegAsm.exe" /unregister "$(TargetDir)$(TargetFileName)"
"C:\Windows\Microsoft.NET\Framework\v4.0.30319\RegAsm.exe" "$(TargetDir)$(TargetFileName)"
Attention: as my computer is x64, there is a specific x64 inside the path of gacutil executable on my machine that may be different on yours.
64bit IE Needs 64bit-compiled and 64bit-registered BHO. Use 64bit RegAsm.exe (usually lives in C:\Windows\Microsoft.NET\Framework64\v4.0.30319\RegAsm.exe)
How this add-on works
It traverses all DOM tree, replacing the text, configured using the button, by itself with a yellow background. If you click on the yellowed texts, it calls a javascript function that was inserted on the page dynamically. The default word is 'browser', so that it matches a lot of them!
EDIT: after changing the string to be highlighted, you must click the URL box and press Enter... F5 will not work, I think that it is because F5 is considered as 'navigation', and it would require to listen to navigate event (maybe). I'll try to fix that later.
Now, it is time to go. I am very tired.
Feel free to ask questions... may be I will not be able to answer since I am going on a trip... in 3 days I'm back, but I'll try to come here in the meantime.
The state for IE extensions is actually pretty sad. You have the old model of IE5 Browser Helper Object (yeah, those infamous BHOs that everyone liked to block back in the day), toolbars and the new accelerators for IE.
Even then, compatibility will break sometimes. I used to maintain an extension for IE6 that broke with IE7, so there are some things that have changed. For the most part, as far as I know (I haven't touch BHOs in years) you still need to code them using Active Template Libraries (kind of like an STL for Microsoft's COM) and well as such is only for C++.
You could do COM Interop with C# and get away with doing it in C# but its probably going to be too hard for what it is worth.
Anyway, if you are interested in coding your own extension for IE (which is plausible if you want to have your extensions available in all major browsers) here are the official Microsoft Resources.
http://msdn.microsoft.com/en-us/library/aa753587(v=vs.85).aspx
And for the accelerators that are new in IE8 you could check this one.
http://msdn.microsoft.com/en-us/library/cc289775(v=vs.85).aspx
I agree the documentation is terrible, and the APIs are quite outdated. Still I hope this helps.
EDIT: I guess I can throw one last source of information here. I was looking through my notes of back when I was working on BHOs. And this is the article that got me started with them. It is kind of old, but has a good explanation of the ATL interfaces that you will be using when working with IE BHOs (IObjectWithSite for example). I think it is pretty well explained and helped me a lot back then.
http://msdn.microsoft.com/en-us/library/bb250436.aspx
I also checked the example that GregC posted. It does work with at least IE8, and it is compatible with VS 2010, so if you want to do C# you can get started there and take a look at Jon Skeet's Book. (C# in Depth 2nd edition) Chapter 13 has a good deal of information about the new features in C# 4 that you can use to make the interaction with COM nicer. (I would still recommend you doing your addin in C++)
Another cool approach would be to check out:
http://www.crossrider.org
It's a framework based on JS with jquery which lets you develop browsers extensions for IE, FF and Chrome using a single common JS code.
Basically the framework does all the nasty work and you're left with writing your applications code.
Developing C# BHOs is a pain-in-the-arse. It involves a lot of icky COM code and p/invoke calls.
I have a mostly finished C# BHO here, which you are free to use the source for whatever you want. I say "mostly", because I never did figure out how to save appdata under IE Protected Mode.
I've been working with IE's webbrowser control for years now, and over the course of them, one name comes up over and over again with helpful postings: Igor Tandetnik
If I were developing an extension, I would target a BHO, and start googling for:
BHO Igor Tandetnik
OR
Browser Helper Object Igor Tandetnik
His postings are often very detailed, and he knows what he is talking about.
You're going to find yourself up to your ears in COM and ATL programming.
For a sample walkthrough, check out:
http://msdn.microsoft.com/en-us/library/ms976373.aspx
I agree with Robert Harvey, C# 4.0 features improved COM interop. Here's a bit of older C# code, in desperate need of a re-write.
http://www.codeproject.com/KB/cs/Attach_BHO_with_C_.aspx
This is an attempt to simplify things by avoiding ATL and going with Spartan COM:
C++ and COM to get BHOs going
If you are not trying to reinvent the wheel, you might try Add In Express for IE . I have used the product for the VSTO stuff, and its pretty good. Also they have a helpful forum and quick support.
It is obviously solved, but for the other users, I would recommend SpicIE framework. I have made my own extension based on it. It only supports Internet Explorer 7/8 officialy, but I tested that on Internet Explorer 6-10 (from Windows XP to Windows 8 Consumer Preview) and it works fine.
Unfortunately there were some bugs in the latest release, so I had to fix them and made my own release:
http://archive.msdn.microsoft.com/SpicIE/Thread/View.aspx?ThreadId=5251
I warmly suggest you this post of Pavel Zolnikov published in 2002!
http://www.codeproject.com/Articles/2219/Extending-Explorer-with-Band-Objects-using-NET-and
It is based on the use of Band objects and is compiled using .Net 2.0.
Source code is provided and opens and compiles well with Visual Studio 2013.
As you will read on the post comments it works perfectly well for IE 11 and on Windows 7 and Windows 10.
It worked perfectly well for me on Windows 7 + SP1 and IE 11
Enjoy!
The question is from 2013, now it's 2020, but it may be helpful for future visitors.
I tried to implement #Miguel Angelo's answer, it didn't worked at the beginning.
There still some settings that you be defined.
on internet explorer (I'm using IE-11) go to Tools-->Internet Options-->Advanced:
Also see this SO question and this example from github
In the Build Events tab, set Post-build events command line to: (x64) is listed below
"C:\Program Files (x86)\Microsoft SDKs\Windows\v7.0A\Bin\NETFX 4.0 Tools\x64\gacutil.exe" /if "$(TargetDir)$(TargetFileName)"
"C:\Windows\Microsoft.NET\Framework64\v4.0.30319\RegAsm.exe" /u "$(TargetDir)$(TargetFileName)"
"C:\Windows\Microsoft.NET\Framework64\v4.0.30319\RegAsm.exe" "$(TargetDir)$(TargetFileName)"
I want the Build Events tab , set Post-build events command line to (32 bit operating system)
"C:\Program Files\Microsoft SDKs\Windows\v7.1\Bin\gacutil.exe" /if "$(TargetDir)$(TargetFileName)"
"C:\Windows\Microsoft.NET\Framework\v4.0.30319\RegAsm.exe" /u "$(TargetDir)$(TargetFileName)"
"C:\Windows\Microsoft.NET\Framework\v4.0.30319\RegAsm.exe" "$(TargetDir)$(TargetFileName)"

C#/.NET app doesn't recognize Environment Var Change (PATH)

In my C# app, I am programmatically installing an Oracle client if one is not present, which requires adding a dir to the PATH system environment variable. This all works fine, but it doesn't take effect until the user logs out/in to windows, or reboots. How can I get my app to recognize and use the new PATH var without this step? Even restarting my app would be better than requiring the user to log out/in.
Supposedly, broadcasting this change to other processes should work. Here's what I've tried, with no success:
using System.Runtime.InteropServices;
private const int HWND_BROADCAST = 0xffff;
private const int WM_WININICHANGE = 0x001a, WM_SETTINGCHANGE = WM_WININICHANGE, INI_INTL = 1;
[DllImport("user32.dll")]
private static extern int SendMessageTimeoutA(int hWnd, uint wMsg, uint wParam, string lParam, int fuFlags, int uTimeout, int lpdwResult);
int rtnVal = 0;
SendMessageTimeoutA(HWND_BROADCAST, WM_SETTINGCHANGE, 0, "Environment", 2, 5000, rtnVal);
I've been told if you stop and restart the process in question, it should pick up these kinds of changes, but restarting my app doesn't do it. I suppose it could be an Oracle issue, that something about Oracle requires the login to recognize the change, I'm not sure. Thanks in advance.
Does Environment.GetEnvironmentVariable("MYVAR", EnvironmentVariableTarget.Machine) not work?
If my app is running elevated then I can
Environment.SetEnvironmentVariable("MYVAR", "cool", EnvironmentVariableTarget.Machine);
//do some other stuff...
Console.WriteLine(Environment.GetEnvironmentVariable("MYVAR", EnvironmentVariableTarget.Machine));
C:\TestApp>>TestApp.exe
cool
I don't know if this will work for other running processes but it should for your app doing the getting/setting
Your problem is only certain apps listen for that message (such as explorer) so it will not be used by your application at all. As the environment is generally inherited then restarting your app from within itself isn't going to help as it will get your current Environment block. If the user restarts from the start menu it will work (assuming the WM_SETTINGCHANGE has been broadcast).
You are best using Environment.GetEnvironmentVariable to read out the current value from the registry and merge it back into you current environment. Basically doing Environment.SetEnvironmentVariable("PATH", Environment.GetEnvironmentVariable("PATH", EnvironmentVariableTarget.Machine) + ";" + (Environment.GetEnvironmentVariable("PATH", EnvironmentVariableTarget.User)));
In our project we setup Oracle Instant Client with use of "install.bat" from Instant Client archive. For example:
install.bat odp.net1x %1 name

Categories

Resources