Thursday, April 26, 2007
How to walk the IIS metabase using WMI and System.Management
using System; using System.Collections; using System.Management; namespace WMITest.MetabaseWalker { ///And here's the output class/// Summary description for Program. /// public class Program { const string serverName = "192.168.0.166"; const string userName = "Administrator"; const string password = "mike"; const string wmiPathToDefaultWebsite = "IIsComputer.Name=\"LM\""; Hashtable outputObjects = new Hashtable(); [STAThread] public static void Main() { Program program = new Program(); program.WalkMetabase(); } private void WalkMetabase() { ConnectionOptions options = new ConnectionOptions(); options.Username = userName; options.Password = password; options.Authentication = AuthenticationLevel.PacketPrivacy; ManagementPath path = new ManagementPath(); path.Server = serverName; path.NamespacePath = "root/MicrosoftIISv2"; ManagementScope scope = new ManagementScope(path, options); using(ManagementObject obj = new ManagementObject( scope, new ManagementPath(wmiPathToDefaultWebsite), null)) { OutputObject(obj); } } private void OutputObject(ManagementObject obj) { outputObjects.Add(obj.Path.RelativePath, new object()); Output.WriteLine(); Output.WriteLine("{0}", true, obj.Path.RelativePath); Output.WriteLine(); WriteProperties(obj.Properties); WriteProperties(obj.SystemProperties); foreach(ManagementObject relatedObject in obj.GetRelated()) { if(!outputObjects.ContainsKey(relatedObject.Path.RelativePath)) { Output.TabIn(); OutputObject(relatedObject); Output.TabOut(); } } } private void WriteProperties(PropertyDataCollection properties) { Output.TabIn(); foreach(PropertyData property in properties) { Output.WriteLine("{0}:\t{1}, \t{2}", property.Name, (property.Value == null) ? "null" : property.Value.ToString(), property.Type.ToString()); WritePropertyAsArray(property); } Output.TabOut(); } private void WritePropertyAsArray(PropertyData property) { if(property.IsArray && property.Value != null) { ICollection propertyArray = property.Value as ICollection; if(propertyArray == null) throw new ApplicationException("can't cast property.Value as ICollection"); Output.TabIn(); if(propertyArray.Count == 0) { Output.WriteLine("No Items"); } int counter = 0; foreach(object item in propertyArray) { ManagementBaseObject managementObject = item as ManagementBaseObject; if(managementObject != null) { Output.WriteLine("{0}[{1}]", property.Name, counter.ToString()); WriteProperties(managementObject.Properties); counter++; } else { Output.WriteLine("{0}", item.ToString()); } } Output.TabOut(); } } } }
using System; using System.IO; namespace WMITest.MetabaseWalker { ////// Summary description for Output. /// public class Output { const string outputPath = @"C:\VisualStudio\WMITest\WMITest.MetabaseWalker\Output.txt"; static int tabs = 0; static Output() { using(File.Create(outputPath)){} } public static void WriteLine() { WriteLine(""); } public static void WriteLine(string format, params object[] args) { WriteLine(format, false, args); } public static void WriteLine(string format, bool writeToConsole, params object[] args) { if(writeToConsole) { Console.WriteLine(new string('\t', tabs) + format, args); } using(StreamWriter writer = File.AppendText(outputPath)) { writer.WriteLine(new string('\t', tabs) + format, args); } } public static void TabIn() { tabs++; } public static void TabOut() { tabs--; } } }
Wednesday, April 25, 2007
IIS Redirects by setting the HttpRedirect metabase property with WMI
http://192.168.0.166/google/You'll get redirected to the Google homepage. The other redirect redirects /bbc/ to the BBC News homepage.
using System; using System.Management; namespace WMITest { public class Program { const string serverName = "192.168.0.166"; const string userName = "Administrator"; const string password = "mike"; const string wmiPathToDefaultWebsite = "IIsWebVirtualDirSetting='W3SVC/1/ROOT'"; const string redirectValue = "*; /google/; http://www.google.com/" + "; /bbc/; http://news.bbc.co.uk/" + ", EXACT_DESTINATION"; public static void Main() { ConnectionOptions options = new ConnectionOptions(); options.Username = userName; options.Password = password; options.Authentication = AuthenticationLevel.PacketPrivacy; ManagementPath path = new ManagementPath(); path.Server = serverName; path.NamespacePath = "root/MicrosoftIISv2"; ManagementScope scope = new ManagementScope(path, options); using(ManagementObject obj = new ManagementObject( scope, new ManagementPath(wmiPathToDefaultWebsite), null)) { Console.WriteLine("{0}", obj.Path.RelativePath); if(obj.Properties.Count == 0) Output.WriteLine("No properties found"); obj.SetPropertyValue("HttpRedirect", redirectValue); obj.Put(); string httpRedirectValue = obj.GetPropertyValue("HttpRedirect").ToString(); Console.WriteLine("HttpRedirect='{0}'", httpRedirectValue); } Console.ReadLine(); } } }The tricky stuff is knowing what value to put into the wmiPathToDefaultWebsite variable. The syntax seems to be
Tuesday, April 24, 2007
URL Rewriting
[ISAPI_Rewrite] RewriteRule /google http://www.google.com [I,R]There’s a free version that does per server URL rewriting, or a ‘pro’ version that costs 99ドル and allows per site configuration. Jeff Altwood talks about here. Update In my next post I realise that I can do the same thing by setting the HttpRedirect property in the IIS metabase. Also Travis Hawkins has a nice article here on using ISAPI_Rewrite. He also mentions another technique I hadn't considered before, of using the 404 error page to redirect unknown page requests to a generic handler. Travis also mentions RemapUrl, a tool that comes with the IIS 6.0 resource kit, but as he says, it's pretty limited.
Friday, April 13, 2007
Resolving mutipart WSDL documents with DiscoveryClientProtocol
using System; using System.IO; using System.Collections; using System.Net; using System.Web; using System.Web.Services; using System.Web.Services.Discovery; using NUnit.Framework; namespace MH.WsdlWorks.Tests { [TestFixture] public class DiscoveryClientProtocolSpike { const string wsdlUrl = "http://localhost:1105/EchoService.svc?wsdl"; [Test] public void DiscoveryClientProtocolTest() { DiscoveryClientProtocol client = new DiscoveryClientProtocol(); client.Credentials = CredentialCache.DefaultCredentials; client.DiscoverAny(wsdlUrl); client.ResolveAll(); foreach (DictionaryEntry dictionaryEntry in client.Documents) { Console.WriteLine("Key: {0}", dictionaryEntry.Key.ToString()); } string myDocuments = Path.Combine( System.Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), "Wsdl"); client.WriteAll(myDocuments, "wsdlDocumentsMap.xml"); } } }
Thursday, April 12, 2007
WCF and WSDL
I've recently started using WCF to develop Web Services. It's a very compelling model that sits much better with a service oriented view of web services than ASMX. The flexibility it gives you is wonderful, you can have any executable host a web service, you can serialize your messages any way you want and you can use any transport. However, if you're just doing standard SOAP, XML over HTTP then it's almost as easy as ASMX.
Part of the change to a more standards oriented web service infratructure in WCF is the way it produces WSDL documents. ASMX by default will always spew out a WSDL document when you appended '?WSDL' to your web services' URL and that WSDL document is always a single file. WCF doesn't create a WSDL document by default, you have to configure that behaviour by adding a serviceMetadata behaviour to your config file:
<serviceBehaviors> <behavior name="myServiceBehaviour"> <serviceMetadata httpGetEnabled="true" /> </behavior> </serviceBehaviors>
Also the default pattern for the WSDL file itself has changed, it follows best practices and devides the WSDL document up by namespaces, so if your portTypes namespace is different from your service namespace, you'll get two WSDL files and the service WSDL will reference the portTypes WSDL with a wsdl:import element. The types schemas are always produced as seperate files, again, one for each namespace.
The problem with factoring a WSDL document into seperate files is that many tools don't understand wsdl:import (or xml:include and xml:import) and will simply choke if you try to feed them it. You can override this default behaviour as Tomas Restrepo outlines in this blog post 'Inline XSD in WSDL with WCF', but of course it requires you to have access to the web service.
This behaviour has been giving me a headache because I'm currently trying to create a generic web service test tool. I'm using System.Web.Services.Description.ServiceDescription to load and parse a WSDL file that's given by the user as a URL, but when I tried to run it against a WCF service is choked because the URL only points to the root WSDL document which is in fact exported as several linked files. It looks like I'm going to have load the WSDL more intelligently. I tried running the wsdl.exe tool against the same URL and it works fine, so there might be solution there, I'll have to open up Reflector and have a look:)
Update: I resoloved how to do this in my next post.
More update: Christian Weyer has an interesting post on WCF and multipart WSDLs. He shows how to get WCF to output a single unfactored WSDL document.