Process.Start and required input

I had an issue with Process.Start today. I was trying to call an executable that requires the user to confirm the action with a keystroke, in this case “y”.

As far as I am aware, there is no mechanism within the .NET framework to deal with this kind of thing, so I had to resort to the following syntax:

echo y| cacls path /E /T /P username:F

Unfortunately, the command “echo” is a pseudo command contained within the command shell.

To get around this, I had to use the following for the process.

Process runner = new Process(); runner.StartInfo.FileName = "cmd.exe"; runner.StartInfo.Arguments = "/C echo y| cacls path /E /T /P username:F"; runner.Start();

There is probably a more elegant way to do this, but that’s what I came up with in the time allowed.

Return multiple values from a method

This falls under the category of “I would have known about this if I was not self-taught, and it would have saved me so much time.”

In general, the methods I have written in the past have only been able to cope with a single return value. This is not a problem if you want, for example, to return a value from a DataSet, or a simple transform. However, there are occasions where you need to do multiple operations on a single set of data. An example might be statistical analysis on a set of integers – return the average, the sum, minimum and maximum values, etc.

I discovered the out keyword today, and it used as follows.

private void GetStatistics(int value1, int value2, out int sum, out int difference) { sum = value1 + value2; if(value1 > value2) difference = value1 - value2; else difference = value2 - value1; } private void process_Click(object sender, System.EventArgs e) { int sum; int diff; GetStatistics(34,82,out sum,out diff); label1.Text = "Sum :: " + sum.ToString(); label2.Text = "Difference :: " + diff.ToString(); }

The out keyword specifies that the parameter is to be returned when the function completes. In this simple example, the result would be that label1 shows “Sum :: 116″, and label2 shows “Difference :: 48″. It is that easy.

You will notice that in this example, there is no return value (since it is a void function), but it can be amended to allow different method types.

private string GetStatistics(int value1, int value2, out int sum, out int difference) { sum = value1 + value2; if(value1 > value2) difference = value1 - value2; else difference = value2 - value1; return "I have finished that for you, sir"; } private void button1_Click(object sender, System.EventArgs e) { int sum; int diff; MessageBox.Show(GetStatistics(34,82,out sum,out diff)); label1.Text = "Sum :: " + sum.ToString(); label2.Text = "Difference :: " + diff.ToString(); }

In this example, you can see that the out parameters do not have to even be the same type as the method. Here we return a string from the method, alongside the sum and difference integer values.

Powerful, I think.

Web Service Portability (part 2)

Another thing that caught me out today… I deployed a couple of web services, and web form consumers of those services to a “Live” environment today, and was getting some strange behaviour. Because of the limited amount of fault tracking code in my application, I spent several hours trying to figure out what my actual error was.

Once I added some more debugging code, I was able to determine the problem, and that it stemmed from a simple source.

The error message I was presented with was
"File or assembly name <random characters>.dll, or one of its dependencies, was not found"

After a quick Google, I discovered that the service account that IIS runs as was not allowed read/write access to the temp directory. As a result it was unable to download executable code from the web service, and therefore died. Lots of resources say to ensure that the ASPNET account has privileges on windows\temp, but you should check that the account really is ASPNET, and that your system temp folder is windows\temp.

Web Service Portability

When you create an application that relies upon web services, the simplest way to define the location of those web services is by specifying them at design time. The problem, of course, is when you want to deploy a development web service on one server to a live environment on another server. The answer? At design time, specify the “URL Behaviour” property of the web reference to “Dynamic”. This will add en entry to the app.config (or web.config) file for each web reference that looks like the following…

<appSettings> <add key="Full.NameSpace.Of.Web.Reference" _ value="http://servername/webservicepath/servicename.asmx"/> </appSettings>

With this in place, it is possible to move the physical location of the web services around without having to recompile your client application.

*CAVEAT* I have heard rumour that if the name of the service includes the string “static”, this can break the functionality described above. I do not know for sure if this is the case, but it is a possible place to start debugging in the event of failure.

Delete an object from the IIS metabase

Simple enough to do, as long as you remember you have to remove it from the parent item’s children list.

public static void DeleteMetabaseObject(string metabasePath) { try { string deleteParent = metabasePath.Substring(0,metabasePath.LastIndexOf("/")); string deleteName = metabasePath.Substring(metabasePath.LastIndexOf("/") + 1); DirectoryEntry parent = new DirectoryEntry(deleteParent); DirectoryEntry childToDelete = new DirectoryEntry(metabasePath); parent.Children.Remove(childToDelete); parent.CommitChanges(); } catch(System.Runtime.InteropServices.COMException e1) { throw e1; } }

Create a new Virtual Directory in IIS via C#

The following method will create a new IIS Virtual Folder object, based on a path and a friendly name.
ServerID is the ID of the “Web Site” you want to work with in IIS.
VirtualDirName is the name of the Folder that will be available through IIS (eg. http://localhost/VirtualDirName)
Path is the physical location of the folder on the disk.
AppPool is the name of the application pool that your new folder will run under.
AccessScript is a boolean value to determine whther the folder will be allowed to access scripts
CreatePhysicalFolder is a boolean to tell the method whether it should create non-existent file system folders.

public static bool CreateNewVirtualDirectory(int ServerId, string VirtualDirName, string Path, _ string AppPool, bool AccessScript, bool CreatePhysicalFolder) { DirectoryEntry Parent = new DirectoryEntry(@"IIS://localhost/W3SVC/" + ServerId.ToString() + "/Root"); DirectoryEntry NewVirtualDir; NewVirtualDir = Parent.Children.Add(VirtualDirName, "IIsWebVirtualDir"); NewVirtualDir.Properties["Path"][ 0] = Path; New if(!Directory.Exists(Path)) { Directory.CreateDirectory(Path); return true; } } else { return true; } return true; }

New Theme

I’ve uploaded the first test run of a new theme today. I know there are still a lot of “issues” with the theme, but it does mean that the <code> tags display in a more user-friendly way now. I shall be tweaking and inserting new classes in the CSS over then next few days / weeks / months / whatever, so there should be an increase in the quality of the presentation.

Manage IIS Security from a C# Assembly

I have discovered over the past couple of days that it is possible to control many aspects of IIS behaviour from a .NET assembly (in this case I will be using C# as the underlying language), by using the System.DirectoryServices namespace.

Using very similar methods to those you would use to get the properties of an Active Directory object, you can get or set properties on IISWebVirtualDirs, IISWebFiles, or any of the other available object types within IIS.

The basic way to select a DirectoryEntry object from IIS is as follows.

DirectoryEntry myObject = new DirectoryEntry("IIS://localhost/W3SVC/1/Root/myAppFolder/myFile.aspx");

The above code contains one of the first problems you will have to surmount when setting the properties on a file. What is the metabase path to the object you want to work on? Fortunately, there are two solutions to this. A manual one, and a more programmatic one.

The manual solution is to look in the %systemroot%\System32\Inetsrv\metabase.xml for the file you are interested in managing. (nb. Be careful with this file. Corrupting it’s contents can break IIS completely.) An example entry for a Virtual Directory will look like this :

<iiswebvirtualdir Location ="/LM/W3SVC/1/ROOT/menu2" AccessFlags="AccessRead | AccessScript" AppFriendlyName="menu2" AppIsolated="2" AppRoot="/LM/W3SVC/1/Root/menu2" AuthFlags="AuthNTLM" DirBrowseFlags="DirBrowseShowDate | DirBrowseShowTime | DirBrowseShowSize | _ DirBrowseShowExtension | DirBrowseShowLongDate | EnableDefaultDoc" Path="D:\TestArea\menu2" >

The “Location” property is the one you will need. Notice, though, that this entry begins “/LM/W3SVC” rather than “IIS://localhost/W3SVC”. You will need to take that into account when you search for the object.

To do this more programmatically, you could use the following snippet to traverse up to 4 levels down your IIS hierarchy.

private void button1_Click(object sender, System.EventArgs e) { treeView1.Nodes.Clear(); treeView1.Nodes.Add("IIS://localhost/W3SVC"); DirectoryEntry root = new DirectoryEntry(@"IIS://localhost/W3SVC"); DirectoryEntries level1 = root.Children; foreach(DirectoryEntry myDI in level1) { TreeNode l1parent = treeView1.Nodes[0].Nodes.Add(myDI.Name); DirectoryEntries level2 = myDI.Children; foreach(DirectoryEntry myDI2 in level2) { TreeNode l2Parent = l1parent.Nodes.Add(myDI2.Name); try { DirectoryEntries level3 = myDI2.Children; foreach(DirectoryEntry myDI3 in level3) { TreeNode l3Parent = l2Parent.Nodes.Add(myDI3.Name); try { DirectoryEntries level4 = myDI3.Children; foreach(DirectoryEntry myDI4 in level4) { l3Parent.Nodes.Add(myDI4.Name); } } catch { Console.WriteLine(l3Parent.Name + " has no child nodes"); } } } catch { Console.WriteLine(l2Parent.Name + " has no child nodes"); } } } }

It would then be fairly simple to find any uniquely named objects within the st
ructure and use that as your base reference path.

If you are trying to modify an object that does not yet have an entry in the metabase (for example, a file with no properties that differ from the folder it resides in) you will have to create a metabase object for that file. The following block will give you an idea how.

public static bool EnsureObjectExists(string metabasePath, string itemType) { try { DirectoryEntry path = new DirectoryEntry(metabasePath); string myName = path.Name; Console.WriteLine("Got it"); return true; } catch(Exception e2) { string newobjectParent = metabasePath.Substring(0,metabasePath.LastIndexOf("/")); string newitemName = metabasePath.Substring(metabasePath.LastIndexOf("/") + 1); bool createdOK = false; switch(itemType) { case "IISWebFile": createdOK = CreateWebFileObject(newobjectParent,newitemName); break; case "IISVirtualFolder": Console.WriteLine("Can't create that. Not written yet."); break; default: Console.WriteLine("Can't create that. Dunno what it is"); break; } if(createdOK) { Console.WriteLine("created the new object"); } else { Console.WriteLine("BARF!!!!!!!!!!"); createdOK; } }

(Of course, I didn’t write the methods to create other types of object. At the time of writing I don’t actually need them, but I will do that at some point.)

So now you know that your object exists (either that, or the method returned false and creation failed.) The next step I will show you is how to restric access to a location to a specific IP address (or multiple addresses).

In order to allow only specific addresses access, you must make sure that the “GrantByDefault” property of the object is set to false.

public static bool SetIPSecurityProperty(string metabasePath, string member, string item) { // metabasePath is of the form "IIS:///" // for example "IIS://localhost/SMTPSVC/1" // member is of the form "IPGrant|IPDeny|DomainGrant|DomainDeny" // item is of the form "", for example, 157.56.236.15 or domain.microsoft.com Console.WriteLine("\nEnumerating the IPSecurity property at {0}:", metabasePath); try { if (("IPGrant" != member) && ("IPDeny" != member) && ("DomainGrant" != member) && ("DomainDeny" != member)) { Console.WriteLine(" Failed in SetIPSecurityProperty; " + "second param must be one of IPGrant|IPDeny|DomainGrant|DomainDeny"); } else { DirectoryEntry path = new DirectoryEntry(metabasePath); path.RefreshCache(); object ipsecObj = path.Invoke("Get", new string[] { "IPSecurity" }); Type t = ipsecObj.GetType(); Console.Write(t.ToString()); Array data = (Array)t.InvokeMember(member, BindingFlags.GetProperty, null, ipsecObj, null); Console.WriteLine(" Old {0} =", member); bool exists = false; Debug.WriteLine("Starting to check the object"); foreach (object dataItem in data) { Console.WriteLine(" {0}", dataItem.ToString()); if (dataItem.ToString().StartsWith(item)) { exists = true; } } if (exists) { Console.WriteLine(" {0} already exists in {1}", item, member); } else { object[] newData = new object[data.Length + 1]; data.CopyTo(newData, 0); newData.SetValue(item, data.Length); t.InvokeMember(member, BindingFlags.SetProperty, null, ipsecObj, new object[] { newData }); path.Invoke("Put", new object[] { "IPSecurity", ipsecObj }); path.CommitChanges(); path.RefreshCache(); ipsecObj = path.Invoke("Get", new string[] { "IPSecurity" }); data = (Array)t.InvokeMember(member, BindingFlags.GetProperty, null, ipsecObj, null); Console.WriteLine(" New {0} =", member); foreach (object dataItem in data) Console.WriteLine(" {0}", dataItem.ToString()); Console.WriteLine(" Done."); return true; } } return false; } catch (Exception ex) { if ("HRESULT 0x80005006" == ex.Message) Console.WriteLine("Property IPSecurity does not exist at {0}", metabasePath); else Console.WriteLine("Failed in SetIPSecurityProperty with the following exception: \n"); Console.WriteLine(ex.Message + Environment.NewLine + ex.ToString()); return false; } }

To call the method above, you should use the following syntax :

SetIPSecurityProperty(metabasePath,"IPGrant","192.168.0.1"); SetIPSecurityProperty(metabasePath,"DomainGrant","test.example.com");

The first call will allow access from a single IP address. Simple. Nothing more to say there. The second line is a bit more interesting. This will allow anyone connecting from the test.example.com to actually get to the file. The reason that this is more interesting is the way IIS handles these rules. Because you have specified a domain, IIS has to do a reverse DNS lookup for each request. In fact, if you set this property through IIS Manager, a popup appears with the text “Warning: Restricting access by domain name requires a DNS reverse lookup on each connecion. This is a very expensive operation and will dramatically affect server performance”. I should try to stick to IP addres rules if I were you.

So that is it as far as setting IP based security properties in IIS via a C# application. There are lots of other properties that can be set on each object, and I will describe those here when I have worked with them some more.