Administering IIS
Suppose you want to make it easy for junior administrators to create new virtual directories under IIS. You need a tool that will create the virtual directory, and automatically assign the designated Web developer as the owner of the new directory. This would be a great tool in a Web development shop, and can save a lot of mouse clicking. Suppose you also want to make sure that only members of a special Web Operators group on the Web server can use the page.
NOTE
This code is adapted in part from the IIS Admin Web site included with IIS 4.0 and 5.0. That site and its source are great examples of how to use the IIS administration object, which is utilized within this script.
Designing the Page
As usual, I start with FrontPage to make the basic HTML. In this case, it's pretty simple, and it's shown in Listing 24.4.
Listing 24.4. IIS.htm. Static HTML for the IIS administration page.
<HTML>
<BODY>
<CENTER>
<FORM ACTION="iis.asp" METHOD="POST">
<INPUT type="text" name="VirtualDir">
<SELECT size=1 name="Developer">
<OPTION VALUE="test">TEST</OPTION>
</SELECT>
<INPUT type="checkbox" name="AllowScript">
<INPUT type="submit" value="Submit" name="Submit">
</FORM>
</CENTER>
</BODY>
</HTML>
Not much to it. The only placeholder information is the TEST option in the drop-down list box; I'll want to populate the list with the members of the Web Developers group.
For a script this complex, I also want to lay out the tasks that I need to accomplish.
Create an IIS virtual directory. Set properties on a virtual directory. Create a physical file folder. Set permissions on a physical file folder. Check to make sure the user logged on. Check to make sure the user is in the Web Operators group. Get a membership list from a local computer group.
You should know how to do all but the first two. Excuse me? You don't know how to set permissions on a folder from within a script? Sure, you do-you just have to be creative. You've probably used, or at least heard of, Cacls.exe, Microsoft's command-line utility for setting NTFS permissions. I'll just use that within my script. Yes, I could use WMI, but working with permissions from within WMI is insanely complicated. I'm not after an "elegant" script, I'm out to get a job done as quickly as possible. Therefore, Cacls.exe it is.
TIP
When you don't know how to do something in script, be creative! If you have free time later-and who does?-you can always use a "prettier" method, but when there's work to be done, just do it in whatever way you know how. If it works, it isn't wrong.
Writing Functions and Subroutines
I've identified several tasks that can be broken up into functions or subroutines. These include creating the IIS virtual directory, creating the physical folder on the hard drive, and a couple of others. Listing 24.5 has the list of functions and subs.
Listing 24.5. IISFandS.vbs. These functions and subs will be called from the main script.
Function IISDirExists(sVirtualDir)
On Error Resume Next
Dim bExists
'create an IIS admin object
'assumes Default Web Site
Set oIISAdmin = GetObject("IIS://localhost/W3SVC/1/Root/"
& sVirtualDir)
'if there's no error, the dir exists
'display message and end
If Err.Number = 0 Then
bExists = True
Else
bExists = False
End If
'release IIS admin object
Set oIISAdmin = Nothing
IISDirExists = bExists
End Function
Function CreateFolder(sVirtualDir)
'get another admin object first
Set oIISAdmin = GetObject("IIS://localhost/W3SVC/1/Root")
sVDirPath = oIISAdmin.Path & "\" & sVirtualDir
'get a filesystemobject
Set oFSO = Server.CreateObject("Scripting.FileSystemObject")
'create the folder on disk if it
'doesn't exist already
If Not oFSO.FolderExists(sVDirPath) Then
oFSO.CreateFolder sVDirPath
End If
'release the filesystemobject
Set oFSO = Nothing
'return the path to the folder
CreateFolder = sVDirPath
End Sub
Sub CreateIISVDir(sVirtualDir, bAllowScript, sVDirPath, _
bInProcess)
Set oVirtualDir = oIISAdmin.Create("IISWebVirtualDir",
sVirtualDir)
oVirtualDir.AccessScript = bAllowScript
oVirtualDir.Path = sVDirPath
oVirtualDir.SetInfo
oVirtualDir.AppCreate bInprocess
End Sub
Sub CheckAuth()
If Request.ServerVariables("LOGON_USER") = "" Then
Response.Status = "401 Authorization Required"
Response.End
End If
End Sub
These probably deserve some explanation. I'll start with IISDirExists(), which checks to see if a virtual directory with a specified name already exists in IIS' Default Web Site.
Function IISDirExists(sVirtualDir)
On Error Resume Next
Dim bExists
'create an IIS admin object
'assumes Default Web Site
Set oIISAdmin = GetObject("IIS://localhost/W3SVC/1/Root/"
& sVirtualDir)
'if there's no error, the dir exists
'display message and end
If Err.Number = 0 Then
bExists = True
Else
bExists = False
End If
'release IIS admin object
Set oIISAdmin = Nothing
IISDirExists = bExists
End Function
This function uses the IIS Administration object. Notice that it works a lot like ADSI: You connect to it using the IIS: provider, specify the server name ("localhost"), the service ("W3SVC"), the instance ("1" is the Default Web Site), and then specify "Root" to connect to the root level. Finally, I tacked on the name of the virtual directory I'm checking on. This call to GetObject fails if the virtual directory doesn't exist, so I issue On Error Resume Next to start with. If the call does fail, I catch it with the If Err.Number = 0 statement. Here's how it works.
If there's no error, the directory exists, and I set bExists to True. If there's an error, the directory doesn't exist, so I set bExists to False.
Finally, I release the Administration object and return my result.
The next function creates a new physical file folder. It accepts the name of the virtual directory, though, and uses an IIS admin object to figure out the correct physical file path. This ensures that the new folder will be created under the Default Web Site's physical root folder (usually C:\Inetpub\Wwwroot).
Function CreateFolder(sVirtualDir)
'get another admin object first
Set oIISAdmin = GetObject("IIS://localhost/W3SVC/1/Root")
sVDirPath = oIISAdmin.Path & "\" & sVirtualDir
'get a filesystemobject
Set oFSO = Server.CreateObject("Scripting.FileSystemObject")
'create the folder on disk if it
'doesn't exist already
If Not oFSO.FolderExists(sVDirPath) Then
oFSO.CreateFolder sVDirPath
End If
'release the filesystemobject
Set oFSO = Nothing
'return the path to the folder
CreateFolder = sVDirPath
End Sub
At the completion of this function, I return the full path of the new folder. I'll use that information again in the next routine, which is a sub. This routine creates the IIS virtual directory and sets its scripting permissions property, its physical path, and its status as an in-process IIS application.
Sub CreateIISVDir(sVirtualDir, bAllowScript, sVDirPath, _
bInProcess)
Set oVirtualDir = oIISAdmin.Create("IISWebVirtualDir",
sVirtualDir)
oVirtualDir.AccessScript = bAllowScript
oVirtualDir.Path = sVDirPath
oVirtualDir.SetInfo
oVirtualDir.AppCreate bInprocess
End Sub
I need one more sub. This one just checks to make sure the current user authenticated and has a user name; if he doesn't, I set Response.Status equal to the "Authorization Required" error message and end the script.
Sub CheckAuth()
If Request.ServerVariables("LOGON_USER") = "" Then
Response.Status = "401 Authorization Required"
Response.End
End If
End Sub
That's the bulk of the script's heavy lifting. There are a couple of other tasks I'll write in the main script, mainly because they require a lot of input parameters and aren't worth writing as a separate function or sub.
Writing the Main Script
Time to add the main script. I still need to populate that drop-down list with members of the Web Developers group, run CACLS, and make sure the current user belongs to the Web Operators group.
IIS Administration
Listing 24.6 shows the full, complete Web page code.
Listing 24.6. IIS.asp. You'll need to make some changes to get this working in your environment.
<%
Function IISDirExists(sVirtualDir)
On Error Resume Next
Dim bExists
'create an IIS admin object
'assumes Default Web Site
Set oIISAdmin = GetObject("IIS://localhost/W3SVC/1/Root/"
& sVirtualDir)
'if there's no error, then the dir exists
'display message and end
If Err.Number = 0 Then
bExists = True
Else
bExists = False
End If
'release IIS admin object
Set oIISAdmin = Nothing
IISDirExists = bExists
End Function
Function CreateFolder(sVirtualDir)
'get another admin object first
Set oIISAdmin = GetObject("IIS://localhost/W3SVC/1/Root")
sVDirPath = oIISAdmin.Path & "\" & sVirtualDir
'get a filesystemobject
Set oFSO = Server.CreateObject("Scripting.FileSystemObject")
'create the folder on disk if it
'doesn't exist already
If Not oFSO.FolderExists(sVDirPath) Then
oFSO.CreateFolder sVDirPath
End If
'release the filesystemobject
Set oFSO = Nothing
'return the path to the folder
CreateFolder = sVDirPath
End Sub
Sub CreateIISVDir(sVirtualDir, bAllowScript, sVDirPath, _
bInProcess)
Set oVirtualDir = oIISAdmin.Create("IISWebVirtualDir",
sVirtualDir)
oVirtualDir.AccessScript = bAllowScript
oVirtualDir.Path = sVDirPath
oVirtualDir.SetInfo
oVirtualDir.AppCreate bInprocess
End Sub
Sub CheckAuth()
If Request.ServerVariables("LOGON_USER") = "" Then
Response.Status = "401 Authorization Required"
Response.End
End If
End Sub
'Ensure authentication occurs
CheckAuth
'handle submitted form
If Request("Submit") <> "" Then
Dim sVirtualDir, bInprocess
Dim oIISAdmin, sVDirPath
Dim oFSO, sDeveloper
Dim oVirtualDir, bAllowScript
Dim sServer, oWSH
Dim oReturnCode, sCmdLine
'get submitted information
sVirtualDir = Request.Form("VirtualDir")
sDeveloper = Request.Form("Developer")
If Request.Form("AllowScript") = "on" Then
bAllowScript = "True"
Else
bAllowScript = "False"
End If
'see if virt dir exists already
If IISDirExists(sVirtualDir) = True Then
Response.Write "That directory exists."
Response.End
End If
'Create the directory in the file system
sVDirPath = CreateFolder(sVirtualDir)
'Create the folder in IIS
CreateIISVDir(sVirtualDir, bAllowScript, sVDirPath, bInProcess)
'build a command line for CACLS
sCmdLine = "cmd /c echo y| CACLS "
sCmdLine = sCmdLine & sVDirPath
sCmdLine = sCmdLine & " /g "
& sDeveloper & ":C"
'create a WSH shell object
Set oWSH = Server.CreateObject("WScript.Shell")
'execute CACLS
oReturnCode = oWSH.Run (sCmdLine , 0, True)
'release the shell object
Set oWSH = Nothing
'report
Response.Write("Done.")
Response.End
End If
%>
<HTML>
<BODY>
<%
Dim sUserADSI, oMachine
Dim oMember, oGroup
Dim sADSI, bLoggedOn
Dim sMember
'get current user
sUserADSI = "WinNT://BRAINCORE/" & _
Request.ServerVariables("LOGON_USER")
'change backslashes to slashes for ADSI
sUserADSI = Replace(sUserADSI, "\", "/")
'get the group
Set oGroup = GetObject("WinNT://DON-TABLET/Web Operators,group")
'is the user a member?
For Each oMember in oGroup.Members
If LCase(oMember.ADsPath) = LCase(sUserADSI) Then
bLoggedOn = True
Exit for
End If
Next
Set oGroup = Nothing
If bLoggedOn = True Then
%><CENTER>
<FORM ACTION="iis.asp" METHOD="POST">
<INPUT type="text" name="VirtualDir">
<SELECT size=1 name="Developer">
<%
'make a list of Web Developers members
Set oGroup = _
GetObject("WinNT://don-tablet/Web Developers,group")
For Each oMember in oGroup.Members
sMember = Replace(oMember.ADsPath, "/", "\")
sMember = Mid(sMember, 9, Len(sMember))
Response.Write "<OPTION VALUE=" & sMember & ">"
Response.Write sMember
Response.Write "</OPTION>"
Next
Set oGroup = Nothing
%>
</SELECT>
<INPUT type="checkbox" name="AllowScript">
<INPUT type="submit" value="Submit" name="Submit">
</FORM>
</CENTER>
<%
Else
%>Your user account:
<% Response.Write sUserADSI %>
does not have access to this page.
<%
End If
%>
</BODY>
</HTML>
You need to make a number of changes to get this to work.
Change "don-tablet" to the name of the local Web server. Change "BRAINCORE" to the name of the domain or workgroup that the Web server belongs to. Make sure the "Web Operators" and "Web Developers" user groups all exist. Make sure the Default Web Site exists in IIS.
IIS Administration-Explained
This page starts with the functions and subs I outlined earlier.
<%
Function IISDirExists(sVirtualDir)
On Error Resume Next
Dim bExists
'create an IIS admin object
'assumes Default Web Site
Set oIISAdmin = GetObject("IIS://localhost/W3SVC/1/Root/"
& sVirtualDir)
'if there's no error, then the dir exists
'display message and end
If Err.Number = 0 Then
bExists = True
Else
bExists = False
End If
'release IIS admin object
Set oIISAdmin = Nothing
IISDirExists = bExists
End Function
Function CreateFolder(sVirtualDir)
'get another admin object first
Set oIISAdmin = GetObject("IIS://localhost/W3SVC/1/Root")
sVDirPath = oIISAdmin.Path & "\" & sVirtualDir
'get a filesystemobject
Set oFSO = Server.CreateObject("Scripting.FileSystemObject")
'create the folder on disk if it
'doesn't exist already
If Not oFSO.FolderExists(sVDirPath) Then
oFSO.CreateFolder sVDirPath
End If
'release the filesystemobject
Set oFSO = Nothing
'return the path to the folder
CreateFolder = sVDirPath
End Sub
Sub CreateIISVDir(sVirtualDir, bAllowScript, sVDirPath, _
bInProcess)
Set oVirtualDir = oIISAdmin.Create("IISWebVirtualDir",
sVirtualDir)
oVirtualDir.AccessScript = bAllowScript
oVirtualDir.Path = sVDirPath
oVirtualDir.SetInfo
oVirtualDir.AppCreate bInprocess
End Sub
Sub CheckAuth()
If Request.ServerVariables("LOGON_USER") = "" Then
Response.Status = "401 Authorization Required"
Response.End
End If
End Sub
Next, I call the CheckAuth routine. This ensures that the user has authenticated; if he hasn't, the sub takes care of sending an appropriate error message and ending the script.
'Ensure authentication occurs
CheckAuth
Now, I need to see if the form has been submitted. For now, let's assume it has been, so the following code will evaluate to a True logical condition.
'handle submitted form
If Request("Submit") <> "" Then
I'll dimension some variables, and then pull information from the Request object into variables. Pulling the information into variables just makes it easier to work with, so I don't have to keep retyping Request("VirtualDir") and so forth.
Dim sVirtualDir, bInprocess
Dim oIISAdmin, sVDirPath
Dim oFSO, sDeveloper
Dim oVirtualDir, bAllowScript
Dim sServer, oWSH
Dim oReturnCode, sCmdLine
'get submitted information
sVirtualDir = Request.Form("VirtualDir")
sDeveloper = Request.Form("Developer")
If Request.Form("AllowScript") = "on" Then
bAllowScript = "True"
Else
bAllowScript = "False"
End If
Now, use the IISDirExists() function to see if the specified virtual directory already exists. If it does, write an error message and end the script.
'see if virt dir exists already
If IISDirExists(sVirtualDir) = True Then
Response.Write "That directory exists."
Response.End
End If
If you've made it this far, you know the directory doesn't already exist in IIS. Call the routine to create the folder in the file system. Note that this function actually checks to see if the folder exists, and only tries to create it if it doesn't exist.
'Create the directory in the file system
sVDirPath = CreateFolder(sVirtualDir)
Because the physical folder exists, the virtual directory can be created in IIS.
'Create the folder in IIS
CreateIISVDir(sVirtualDir, bAllowScript, sVDirPath, bInProcess)
Next, assemble a command-line string to execute CACLS. This makes the designated developer the owner of the physical folder, so that he or she can manipulate the files in the folder.
'build a command line for CACLS
sCmdLine = "cmd /c echo y| CACLS "
sCmdLine = sCmdLine & sVDirPath
sCmdLine = sCmdLine & " /g "
& sDeveloper & ":C"
To launch the command line, you need a reference to the Windows Script Host's Shell object.
'create a WSH shell object
Set oWSH = Server.CreateObject("WScript.Shell")
Use the Shell object to execute CACLS.
'execute CACLS
oReturnCode = oWSH.Run (sCmdLine , 0, True)
Finish the script and write a completion message.
'release the shell object
Set oWSH = Nothing
'report
Response.Write("Done.")
Response.End
End If
Everything up to now only executes when the script is completed. If the script wasn't submitted, the following is executed.
%>
<HTML>
<BODY>
<%
Dim sUserADSI, oMachine
Dim oMember, oGroup
Dim sADSI, bLoggedOn
Dim sMember
That gets variable definitions out of the way. Because the first thing we need to do is make sure the user is a member of Web Operators, we need to get the user's logon name. The Request object provides a ServerVariables collection, which provides access to a number of useful data-including the name of the logged-on user. I'm prefixing that logon name with a string that will turn it into an ADSI path.
'get current user
sUserADSI = "WinNT://BRAINCORE/" & _
Request.ServerVariables("LOGON_USER")
We're going to be feeding that path to ADSI, so we need to flip any backslashes into regular slashes. The LOGON_USER variable returns something to the effect of domain\user, but ADSI expects to see domain/user instead. The Replace() function accomplishes the switch in one step.
'change backslashes to slashes for ADSI
sUserADSI = Replace(sUserADSI, "\", "/")
I use ADSI to get a reference to the Web Operators group.
'get the group
Set oGroup = GetObject("WinNT://DON-TABLET/Web Operators,group")
Now, I iterate through each member of the group. For each one, I check to see if the member's ADSI path matches the one I built for the user. If it does match, I set a variable to True and exit the For Each…Next loop.
'is the user a member?
For Each oMember in oGroup.Members
If LCase(oMember.ADsPath) = LCase(sUserADSI) Then
bLoggedOn = True
Exit for
End If
Next
I can now release the ADSI group object. If bLoggedOn equals True, I know the user logged on as a member of the Web Operators group, and I can continue.
Set oGroup = Nothing
If bLoggedOn = True Then
The next few lines are straight from my original static Web page design.
%><CENTER>
<FORM ACTION="iis.asp" METHOD="POST">
<INPUT type="text" name="VirtualDir">
<SELECT size=1 name="Developer">
It's time to fill in that drop-down list of Web Developer members. I use code similar to what I just used to check the Web Operators group: query ADSI for the group, and add each member as an <OPTION> to the drop-down list box.
<%
'make a list of Web Developers members
Set oGroup = GetObject("WinNT://don-tablet/Web Developers,group")
For Each oMember in oGroup.Members
sMember = Replace(oMember.ADsPath, "/", "\")
sMember = Mid(sMember, 9, Len(sMember))
Response.Write "<OPTION VALUE=" & sMember & ">"
Response.Write sMember
Response.Write "</OPTION>"
Next
Set oGroup = Nothing
%>
Finishing the static HTML also finishes the code that executes if the user is a member of Web Operators.
</SELECT>
<INPUT type="checkbox" name="AllowScript">
<INPUT type="submit" value="Submit" name="Submit">
</FORM>
</CENTER>
<%
Now, I include an Else and the code to execute if the user isn't a member of Web Operators.
Else
%>Your user account:
<% Response.Write sUserADSI %>
does not have access to this page.
<%
End If
%>
</BODY>
</HTML>
That's it! The script should execute perfectly, after you make the minor changes I mentioned earlier. This should also serve as a great example of working with IIS from script, and of how to design an administrative Web page.
|