Attempt to create 'Virtual Directory' creates 'Virtual Application' instead - c#

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.

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);
}
}

Changing virtual path and enabling/disabling sites in code

I am working on a web project that contains three web services and a website. My source code is stored in TFS for version control and I regularly have to work in several different branches on the same code. These branches are merged regularly and are all set up in the same way. They all use WebDev.WebServer as default host. Which is fine because it saves other teammembers from having to continuously alter settings in IIS to switch to the proper folder.
Well, guess what?
I want to move to IIS and still be able to use the same config for every branch. This will results in conflicts since I Need to change the virtual folders for these projects every time when I switch branches. I also need to be able to turn off these IIS sites to enable the use of WebDev. Manually, that's a bit of work and I'm a programmer. This needs to be automated...
My idea is to create a small web application running on my local host, which I can use to change the virtual folder and/or to turn on/off the IIS site. Sounds simple enough so all I need are two things:
How do I change the virtual folder of an IIS site from one location to another in code?
How to turn on and off a site in IIS?
Simple, right? So, point three: do you have any other suggestions that I could use?
Oh, other developers are also working on the same project. Not all of them will use IIS to run it. Thus I cannot alter the config files for these projects. I must manage it all through IIS.
Personally, I would use Powershell in this instance.
$websitePath = "IIS:\\Sites\Path to your website in iis"
$fullWebSiteFilePath = "file path to your content"
if(-not (test-path $websitePath))
{
new-item $websitePath -physicalPath $fullWebSiteFilePath -type Application
}
else
{
Set-ItemProperty $websitePath -name physicalPath -value $fullWebSiteFilePath
}
with a little jigerry pokery you could read the different configurations from an xml file and then call the shell script passing the xml file name as a parameter.
To manage IIS7 programmatically, you can start with the ServerManager class.

Modifying scriptmaps/handlermappings programmatically

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.

PowerPoint.Application not raising events in C#

I have a simple application written in C# and .Net 2.0 that displays several PowerPoint 2003 files in a loop. (It is going to be used for a information board in our cafeteria) The application works fine on my development machine but when I deploy it to another machine the events I have registered for SlideShowNextSlide and PresentationClose are never fired. I have tried registering the events with this method.
private void InitPPT()
{
app.SlideShowNextSlide += new Microsoft.Office.Interop.PowerPoint.EApplication_SlideShowNextSlideEventHandler(app_SlideShowNextSlide);
app.PresentationClose += new Microsoft.Office.Interop.PowerPoint.EApplication_PresentationCloseEventHandler(app_PresentationClose);
app.SlideShowEnd += new Microsoft.Office.Interop.PowerPoint.EApplication_SlideShowEndEventHandler(app_PresentationClose);
}
And with this method that I found here:
private void InitPPT()
{
IConnectionPointContainer oConnPointContainer = (IConnectionPointContainer)app;
Guid guid = typeof(Microsoft.Office.Interop.PowerPoint.EApplication).GUID;
oConnPointContainer.FindConnectionPoint(ref guid, out m_oConnectionPoint);
m_oConnectionPoint.Advise(this, out m_Cookie);
}
Do I need to register some dll's on the client machine or am I missing something.
I think this was due to the fact that I was trying to run my application from a local user account but impersonate a domain account so I could access network drives. I have changed my application so it only impersonates the domain account while preforming network operations and not at application startup.

Why do I get E_ACCESSDENIED calling a COM+ method from a proxy?

Sorry if this is a bit long whinded... consider this:
I have a COM+ application in a namespace called Company that exposes an object called Server which has the following methods:
bool Server.Execute(IOptions options)
IOptions Server.CreateOptions()
IOptions simply has a couple of boolean read/write properties as follows:
IOptions.Option1 (bool)
IOptions.Option2 (bool)
I create a client application with the following code:
Company.Server s = new Company.Server();
Company.IOptions serverOptions = s.CreateOptions();
serverOptions.Option1 = false;
serverOptions.Option2 = true;
s.Execute(serverOptions);
I install the COM+ application on machine A and then execute the client on machine A and all is well.
I then modify the client application so that it creates its own implementation of IOptions as follows:
public class ClientOptions : Company.IOptions
{
public bool Option1 { get; set; }
public bool Option2 { get; set; }
}
Company.Server s = new Company.Server();
ClientOptions clientOptions = new ClientOptions();
clientOptions.Option1 = false;
clientOptions.Option2 = true;
s.Execute(clientOptions);
Again, I execute the client application on machine A and all is well.
If I install the COM+ application on machine B as a proxy to machine A and then execute the client, I get an E_ACCESSDENIED error on the call to:
s.Execute(clientOptions);
Here's a summary of the code executing on machine B accessing machine A
Company.Server s = new Company.Server();
Company.Options serverOptions = s.CreateOptions()
serverOptions.Option1 = false;
serverOptions.Option2 = true;
s.Execute(serverOptions); // this is fine
ClientOptions clientOptions = new ClientOptions();
clientOptions.Option1 = false;
clientOptions.Option2 = true;
s.Execute(clientOptions); // this causes the error
To summarise, why can I implement my own IOptions and use it in the COM+ application when the client is on the same machine as the COM+ application but not when the client is accessing the COM+ application via a proxy on another machine?
It seems that if the IOptions was created by the server then it's fine, but if it's created by the client then it's not.
Any help would be greatly appreciated.
Thanks,
Carl.
I going to extrapolate some older experience with DCOM which may or may not be helpful. When you get access denied, you have to look at the DCOM configuration parameters on machine B.
On older OS's (Windows 2000) you would run dcomcnfg. But in XP, you run Component Services from the Control Panel -- Administrative Tools. Under Vista, apparently, you have to run windows\System32\comexp.msc.
When you are in the Component Services administration, highlight My Computer and choose properties. The first thing you have to do is on the Defaults Properties Tab, click Enable Distributed COM on this Computer. Additionally, you may need to specify Launch and Activate and Access permissions in the COM Security tab. When doing so you may have to deal with both Edit Limits and Edit Defaults?
Please note, that I believe that these are DEFAULT permissions and that you may want to find your registered COM+ application in the application list and set application-specific access rights rather than machine-wide defaults. But I know you have to check that Enable Distributed COM on this Computer checkbox.
I hope this helps.
Try looking at the COM server's remote activation permissions on the remote machine via dcomcnfg.exe (should open up MMC snapin).
-Oisin

Categories