Modifying scriptmaps/handlermappings programmatically - c#

I have some code to create a Virtual Directory programmatically.
I need to edit handler mappings of framework 2.0 to use .net 4.0 (basically run .net 2.0 code under .net 4.0 CLR).
The code below works fine under win 2003 server, but on Windows 2008 server webservicefactoryHandler2.0 is renamed as webservicefactoryHandler4.032_1245.
I don't know how to access/edit this name or infact retain the name and just change its value to \Microsoft.NET\Framework\v4.0.30319\aspnet_isapi.dll from \Microsoft.NET\Framework\v2.0.50727\aspnet_isapi.dll.
Dim scriptMapVals As PropertyValueCollection = VDir.Properties("ScriptMaps")
Dim objScriptMaps As ArrayList = New ArrayList()
Dim frameworkVersion As String = "4.0.30319"
Dim versionRegex As Text.RegularExpressions.Regex = New Text.RegularExpressions.Regex("(?<=\\v)\d{1}\.\d{1}\.\d{1,5}(?=\\)")
''Assuming the version will always be something like n.n.nnnnn
Dim scriptMapVal As String
For Each scriptMapVal In scriptMapVals
If scriptMapVal.Contains("Framework") AndAlso scriptMapVal.Contains("asmx") Then
objScriptMaps.Add(Text.RegularExpressions.Regex.Replace(scriptMapVal, versionRegex.ToString, frameworkVersion))
Else
objScriptMaps.Add(scriptMapVal)
End If
Next
VDir.Properties("ScriptMaps").Value = objScriptMaps.ToArray()
VDir.CommitChanges()
Update:
#kev : So basically you are saying to use different code for different version of IIS right? Well while using import "Microsoft.Web.Administration.dll" , do i need to pack it up in my build ? or this is part of all IIS7.0 installables? as Some poeple could have IIS7.0 on Windows XP.
Thanks
gauls

First of all, don't use the IIS6 compatibility API to manipulate IIS7 handler mappings. Use the Microsoft.Web.Administration managed API instead.
However, you're going about this in completely the wrong way. You shouldn't be touching the handler mappings but instead change the managed runtime version for the application pool in in which the application resides.
Add a reference to:
C:\Windows\System32\inetsrv\Microsoft.Web.Administration.dll
Then in your code:
using Microsoft.Web.Administration;
...
using (ServerManager serverManager = new ServerManager())
{
ApplicationPool appPool =
serverManager.ApplicationPools.Where(a => a.Name.Equals(appPoolName))
.Single();
appPool.ManagedRuntimeVersion = "v2.0";
serverManager.CommitChanges();
}
I would also recommend NOT using the ADSI compatibility API's at all to manipulate IIS7's configuration. Learn to love and embrace <applicationHost> and forget about thinking in terms of metabase objects in IIS7:
http://www.iis.net/ConfigReference/system.applicationHost
Update:
Further to your question about having code that works with both IIS6 and IIS7 I would thoroughly recommend NOT using the IIS6 compatibility layer for the convenience of having to maintain just one codebase.
The reason for this is that features of IIS7 objects such as HandlerMappings (the equivalent of Script Mappings) are handled differently. The compatibility shim creates what's known as ABO Custom Map objects which lose some of the benefits of proper HandlerMappings. Whilst they work they will create an administrative nightmare in the longer term. Trust me, I've been there.
If you need to detect which version of IIS your running then here is a handy function to do that:
public static int GetIIsMajorVersion()
{
string regKey = #"SOFTWARE\Microsoft\InetStp";
using(RegistryKey key = Registry.LocalMachine.OpenSubKey(regKey, true))
{
return Convert.ToInt32(key.GetValue("MajorVersion"));
}
}
If the return value is 6 then it's IIS6, if it's 7 then you're on IIS 7.x.

Related

Checking whether a folder exists under an IIS application using C#

I'm working on a web based deployment tool in C# which deploys applications remotely on IIS 7.
I've reached a point where where I'm able to deploy an application. Now I need to see if the application that is deployed has a a certain directory before attempting to set permissions on it (Since the tool would deploy different applications which may or may not have that folder).
There are two approaches that I took:
I've checked for classes that I can use under the ServerManager namespace. I can get a handle on an application deployed under a certain application pool using:
var iis = ServerManager.OpenRemote("serverName")
var iisApplication = iis.Sites[site].Applications["appName"];.
Now I can get the virtual directories under the application using :
var virtualDirectory = iisApplication.VirtualDirectories;
But then I'm not able to see a whole lot of folders which are under that virtual directory. For axample, my application is deployed as test and iisApplication.VirtualDirectories.First() gives me /test. I was want to be able to /test/_ApplicationLogs which is the directory I want to set permissions on.
My next approach was to use DirectoryEntry. Here, I'm not able to figure out the metabase path to use for my application. Is there a standard metabase path used for IIS 7?
For an application called test deployed locally, what would the metabase path be? And would I be able to get all the children so that I can use DirectoryEntry.Exists?
For now, I have a workaround. I can use the WhatIf (set it true) property under DeploymentSyncOptions, do a sync and then check if an object got added. If it did, the directory does not exist. Code :
var syncOptions = new DeploymentSyncOptions();
syncOptions.WhatIf = true;
using (deploymentObject)
{
var result = deploymentObject.SyncTo(
DeploymentWellKnownProvider.SetAcl,
"Default Web Site/path_to_folder",
destinationBaseOptions,
syncOptions);
if (result.ObjectsAdded != 0)
{
syncOptions.WhatIf = false;
deploymentObject.SyncTo(DeploymentWellKnownProvider.SetAcl,
"Default Web Site/path_to_folder",
destinationBaseOptions,
syncOptions);
}
}

Attempt to create 'Virtual Directory' creates 'Virtual Application' instead

I have a web application that deploys with two virtual directories under it's IIS virtual application. On IIS 6 boxes, the following code creates these virtual directories as expected, however on IIS 7 boxes, I end up with my virtual application having two other virtual applications under it, rather than one virtual application with two virtual directories under it. I've tried the following two methods, but both still create a virtual application, not a virtual directory. How can this code be changed to deploy the needed virtual directories, not the undesired virtual applications?
one:
private void AddVirtualDir(DirectoryEntry entry)
{
DirectoryEntry virtualDirectory = (DirectoryEntry)entry.Invoke("Create", "IIsWebVirtualDir", "VirtualDirectory");
virtualDirectory.InvokeSet("Path", #"VirtualPath");
virtualDirectory.InvokeSet("AppFriendlyName", "VirtualDirectory");
virtualDirectory.Properties["AccessRead"][0] = true;
virtualDirectory.Properties["AccessScript"][0] = 512;
virtualDirectory.Properties["AppIsolated"].Clear();
virtualDirectory.Properties["AppIsolated"].Add(2);
virtualDirectory.Invoke("AppCreate", false);
virtualDirectory.CommitChanges();
entry.CommitChanges();
}
two:
private void AddVirtualDir(DirectoryEntry entry)
{
var virtualDirectory = entry.Children.Add("VirtualDirectory", "IIsWebVirtualDir");
virtualDirectory.Properties["AccessRead"][0] = true;
virtualDirectory.Properties["AccessScript"][0] = 512;
virtualDirectory.Properties["AppFriendlyName"][0] = "EditorControls";
virtualDirectory.Properties["AppIsolated"][0] = 2;
virtualDirectory.Properties["Path"][0] = Path.Combine(_INSTALLDIR, #"Kryptiq_Root\FormManagement\EditorControls");
virtualDirectory.CommitChanges();
entry.CommitChanges();
}
How this works on IIS6
The problem here is that you're setting the AppIsolated value. In IIS6 this is used to configure how an application should run, and generally you should never need to touch this or add it anywhere.
AppIsolated always defaults to 2 which means pooled process, i.e. the application will run in either the parent application's application pool or in the pool specified by AppPoolId.
The reason that there are other values is so that you can configure an application to run in a couple of legacy IIS5 modes - In Process and Out of Process mode.
So unless you configured your site's /root application to run as anything other than AppIsolated="2" then you don't need to set this value.
Your code can be as simple as:
using (var entry = new DirectoryEntry("IIS://localhost/W3SVC/1/ROOT"))
{
using (DirectoryEntry virtualDirectory = entry.Children.Add("MyVdir",
"IIsWebVirtualDir"))
{
virtualDirectory.Properties["Path"][0] = PATH_TO_MY_STUFF;
virtualDirectory.Properties["AccessRead"][0] = true;
virtualDirectory.Properties["AccessScript"][0] = 512;
virtualDirectory.CommitChanges();
}
}
If you do set AppIsolated in IIS6 it gets ignored because for the directory to become an application you also need to set AppRoot.
IIS7 - IIS6 compatibility shim
In IIS7 when using System.DirectoryServices you're working with an underlying II6 compatibility API which is translating these ADSI calls to calls to the new IIS7 API. It's not perfect and I suspect that when it see's AppIsolated being set it's assuming you want an application, despite you not specifying any other application related metabase values.
IIS7 Managed API is better
You probably know this, but it's better to work with IIS7 configuration via the managed Microsoft.Web.Administration bits. Not all of the ADSI/metabase compatibility settings have equivalents in IIS7 which can force the translation layer to make compromises to work around this. I mention these types of problems in my answers here and here.

Create an application pool that uses .NET 4.0

I use the following code to create a app pool:
var metabasePath = string.Format(#"IIS://{0}/W3SVC/AppPools", serverName);
DirectoryEntry newpool;
DirectoryEntry apppools = new DirectoryEntry(metabasePath);
newpool = apppools.Children.Add(appPoolName, "IIsApplicationPool");
newpool.CommitChanges();
How do I specify that the app pool should use .NET Framework 4.0?
I see from the tags you're using IIS7. Unless you absolutely have to, don't use the IIS6 compatibility components. Your preferred approach should be to use the Microsoft.Web.Administration managed API.
To create an application pool using this and set the .NET Framework version to 4.0, do this:
using Microsoft.Web.Administration;
...
using(ServerManager serverManager = new ServerManager())
{
ApplicationPool newPool = serverManager.ApplicationPools.Add("MyNewPool");
newPool.ManagedRuntimeVersion = "v4.0";
serverManager.CommitChanges();
}
You should add a reference to Microsoft.Web.Administration.dll which can be found in:
%SYSTEMROOT%\System32\InetSrv
newpool.Properties["ManagedRuntimeVersion"].Value = "v4.0";
Will do the same thing as the Microsoft.Web.Administration.dll but using DirectoryEntry
Also
newPool.InvokeSet("ManagedPipelineMode", new object[] { 0 });
Will switch to integrated or classic pipeline mode using DirectoryEntry.
The other answers are better in your particular scenario, but in general keep in mind that you can use the appcmd tool to do this: https://technet.microsoft.com/en-us/library/cc731784%28v=ws.10%29.aspx. Specifically:
appcmd add apppool /name: string /managedRuntimeVersion: string /managedPipelineMode: Integrated | Classic

Control IIS 7 server from Windows 2003 server programmatically

We run various jobs using a Windows 2003 server. Some of these jobs send app pool commands to web servers running IIS 6 (recycle, start, stop). Now we have a Windows 2008 web server running IIS 7, and we want to send the same commands. This is all done using C#.
This is the code we use to send commands for IIS 6:
var methodToInvoke = "Stop"; // could be "Stop", "Start", or "Recycle"
var co = new ConnectionOptions
{
Impersonation = ImpersonationLevel.Impersonate,
Authentication = AuthenticationLevel.PacketPrivacy
};
var objPath = string.Format("IISApplicationPool.Name='W3SVC/AppPools/{0}'", appPoolName);
var scope = new ManagementScope(string.Format(#"\\{0}\root\MicrosoftIISV2", machineName), co);
using (var mc = new ManagementObject(objPath))
{
mc.Scope = scope;
mc.InvokeMethod(methodToInvoke, null, null);
}
This code doesn't work for IIS 7 due to underlying changes, so we're currently trying this:
using (ServerManager serverManager = ServerManager.OpenRemote(machineName))
{
var appPool = serverManager.ApplicationPools[appPoolName];
if (appPool != null)
{
appPool.Stop(); // or app.Start() or app.Recycle()
serverManager.CommitChanges();
}
}
The above code works fine on my workstation, which runs Windows 7 (and, thus, IIS 7.5). However, it does not work when I deploy this code to our application server. It get this error:
System.InvalidCastException:
Unable to cast COM object of type 'System.__ComObject' to interface type
'Microsoft.Web.Administration.Interop.IAppHostWritableAdminManager'.
This operation failed because the QueryInterface call on the COM component for the
interface with IID '{FA7660F6-7B3F-4237-A8BF-ED0AD0DCBBD9}' failed due to the following error:
Interface not registered (Exception from HRESULT: 0x80040155).
From my research, this is due to the fact that IIS 7 is not available on the Windows Server 2003 server. (I did include the Microsoft.Web.Administration.dll file.)
So my questions are:
Is it possible for the above code for IIS 7 to work at all from a Windows 2003 server?
If no to #1, is there a better way of doing this?
From reading around it doesn't appear to be possible to do what you're looking for. It's not enough to include the dll files.
According to http://forums.iis.net/t/1149274.aspx..
In order to use Microsoft.Web.Administration you need to have IIS installed, at the bare minimum you need to install the Configuration API's which are brought through installing the Management Tools.
Unfortunately there is no SDK that enables this and it has several dependencies on other components that wouldn't let you just take it to another machine and make it work (such as COM objects, DLL's, etc).
I'd be interested in knowing if you've found a way round this.
Thanks
Try controlling the IIS pool with DirectoryEntry instead.
See this topic:
Check the status of an application pool (IIS 6) with C#
Microsoft.Web.Administration, it relies on System.Web.dll which was provided by framework 4, not client profile.

Programmatic check if the IIS6 compatibility role is enabled/disabled in IIS7

How can I check with C# if the IIS6 compatibility role is enabled/disabled on IIS7 ?
you can check read the value in the registry
HKEY_LOCAL_MACHINE\Software\Microsoft\InetStp\Components\WMICompatibility
or, you can ouput the content of servermanagercmd to an xml file and parse that file looking for the iis6 compatibility component
ServerManagerCmd -query [SaveFile.xml]
If your doing this on R2, servermanagercmd is now deprecated so you might want to use powershell to achieve the same check.
Here are some powershell examples, in this case done remotely http://www.techmumbojumblog.com/?p=217
The WMI approach from the previous answer is probably good as well, espcialy if you have more configuration tasks to perform on the IIS, after validating that the compatibility tool is install.
btw, if you do find configuration settings that are not handled by the compatibility component, here is what I found doing it from C#, what I was configuring through wmi back in iis6 worked fine at the website level and under(website, virtual dir and pools), but to configure the webserver level, I had to use the the api that's installed with iis7, Microsoft.Web.Administration.dll from System32\inetsrv.
using Microsoft.Web.Administration;
Please, someone give a good answer for this!
As motivation, here's a very bad answer =)
// Ok, this is stupid, but I can't find any other way to do this
// Detect whether we're in integrated mode or not
#warning GIANT HACK FOR IIS7 HERE
try
{
var x = HttpContext.Current.CurrentNotification;
_isIntegratedMode = true;
}
catch (PlatformNotSupportedException)
{
_isIntegratedMode = false;
}
catch (NullReferenceException)
{
_isIntegratedMode = true;
}
This is what our code currently does to figure this out (yes, I know it's appallingly bad - hence the warnings)
You probably can do that by programmatically querying the WMI provider of IIS7. http://learn.iis.net/page.aspx/162/managing-sites-with-iis-7039s-wmi-provider/
I don't know if you can do that through powershell.

Categories