Miscellaneous Debris

Avner Kashtan's Frustrations and Exultations

May 2007 - Posts

Granting User Rights in C#

As a follow-up to my previous post about changing a Windows Service's user account - there was one think I forgot.

In order for a user to run a service, that user must be granted the "Run as Service" right by the operating system. Usually it's not a problem - the Services MMC snap-in automatically adds that right to a user we choose, and the ServiceProcessInstaller that we get when creating services in .NET also contains code to grant that right. The problem is when we're doing it ourselves through our code - the Change() method on the Win32_Service WMI object doesn't grant that right, and the only other WMI classes that seemed relevant, the RSoP classes, seem to be read-only classes that give us auditing and reports on what user rights are granted, but don't let us manage them.

In other words, it's back to P/Invoking. This time - the LsaOpenPolicy, LsaAddAccountRights and LsaClose methods.

Luckily for me, the work was already done. Much furious googling led me to a two year old forum post by MVP Willy Denoyette, with his managed wrapper class around these Win32 methods. It was written for .NET 1.1, probably, and uses some P/Invoke calls for things that have since been incorporated into .NET 2.0, like getting account SIDs. Never mind. It works fine, and that's all I need.

Technically, I could build a list of constants for the specific logon rights, but for now this is the only one I need:

private static void GrantLogonAsServiceRight(string username)
{
   using (LsaWrapper lsa = new LsaWrapper())
   {
      lsa.AddPrivileges(username, "SeServiceLogonRight");
   }
}

Posted Thursday, May 10, 2007 12:36 PM by Avner Kashtan | with no comments

Filed under: , , ,

Setting Windows Service Account - C# and WMI

I've been busting my brains for over two hours trying to accomplish a relatively simple task - changing the username and password of a currently running Windows Service. Should be trivial enough, but the managed ServiceController class doesn't give me that ability, so I had to resort to more esoteric solutions.

Assuming I wish to avoid direct registry manipulation - an easy enough choice to make - I can either P/Invoke the ChangeServiceConfig method (from advapi32.dll) or use System.Management and call the Win32_Service WMI class. I decided to use as little interop as possible and headed down the WMI path.

Now, Win32_Service doesn't have a property for the user password, which makes sense for security reasons. What I can do is invoke the Change method on it, passing it the username and password. Unfortunately, Change accepts 11 different parameters that can be changed, most of which I wish to leave untouched.

I tried using MgmtClassGen.exe to generate a strongly-typed wrapper around Win32_Service, but the Change method is created for me needed work - it asked for an ErrorControl parameter as a Byte, for instance, but the ErrorControl property was strongly typed as a String - I had to start tweaking the parameters myself, receiving strange exceptions and potentially modifying properties I didn't want to.

 Most code samples I could find refer to VBscript, which can simply not pass the parameters it doesn't want, but C# isn't as flexible. Ultimately, though, I decided to simulate this approach by simply passing null for any parameters I want untouched. This requires me to work without the MgmtClassGen class, and invoke the Change method untyped, like this. I can't say it's code I'm especially proud of, but it gets the work done.

string objPath = string.Format("Win32_Service.Name='{0}'", serviceName);
using (ManagementObject service = new ManagementObject(new ManagementPath(objPath)))
{
   object[] wmiParams = new object[11];
   wmiParams[6] = username;
   wmiParams[7] = password;
   service.InvokeMethod("Change", wmiParams);
}

Posted Tuesday, May 08, 2007 10:54 AM by Avner Kashtan | 9 comment(s)

Filed under: , , ,

Custom ConfigurationSectionHandlers in InstallerClasses

I'm writing an installer class for an MSI Setup Project, and find myself needing to manipulate my web.config containing a custom ConfigurationSection. This should be trivial seeing as my SectionHandler code is bundled with the rest of the application, but fails miserably on my GetSection() call, because the DLL containing it is unavailable to MSIEXEC, which runs from C:\Windows\System32. What to do, what to do?

Several options:

1) Rather than using the custom ConfigurationSection, I can open my web.config as a standard XmlDocument and do the changes manually.

Pro: No depenencies.
Con: More complicated, untyped, more error-prone.

2) Copy my section handler DLL to %SystemRoot%\System32 during installation.

Pro: Simple.
Con: Ugly, goes against the "Don't touch System32" philosophy that serves us well.

3) Launch a new AppDomain whose BaseDirectory points to my bin path, and do all my config-mangling there.

Pro: Clean, strongly typed, etc.
Con: More work. Causes my relatively simple installer class to grow in size and complexity.

 

Right now, I'm leaning towards #2. Keeping it simple, even if ugly. After all, no-one really looks in System32, and uninstalling the application will uninstall those files as well. No harm done.

Anyone got any other approaches, mitigating factors or other suggestions?

Posted Monday, May 07, 2007 10:02 AM by Avner Kashtan | 1 comment(s)

Filed under: , ,

InstallerClasses and little-known gems.

I'm writing an Installer class for an MSI installer project, and passing a checkbox's value from the MSI UI. I noticed that when I pass the checkbox's linked variable into my code, it's received as a string, and not only a string - the possible values are "1" and "0".

This is where I started cursing softly, because as well know, the bool.Parse method can only accept the strings "True" and "False" to parse, and not the commonly-found "1" and "0". Sure, I can put in a line such as this:

bool useSetting = Context.Parameters["MyParam"] == "1";

but you'll have to agree that it's much uglier than:

bool useSetting = bool.Parse(Context.Parameters["MyParam"]);

At least as far as I'm concerned. C/C++ veterans probably have no issue with the first one, but I take offense at this weakly typed pain.

Luckily, it would seem, the .NET class developers felt my pain, and decided that while this icky string comparison is necessary, there's no reason for me to see it. The InstallContext class contains a method called IsParameterTrue which does exactly this sort of string comparison, only a bit more robust than I would have bothered to write. So now I can just use this:

bool useSetting = Context.IsParameterTrue("MyParam");

and feel good about myself.

Posted Sunday, May 06, 2007 6:55 PM by Avner Kashtan | with no comments

Filed under: , ,

More Posts