實務上常有多個Web Application裝在同一台IIS主機的情形,於是我們常會有列舉IIS上所有Web Application清單以便進一步管理、維護的需求。

在IIS7上,微軟推出了Microsoft.Web.Administration.dll,支援用LINQ的方式查詢及修改網站設定(使用方法可參考這篇文章)。無奈在我的工作環境裡,還有很多IIS 6,甚至IIS 5,仍健壯地活著。因此我也只能含淚送Microsoft.Web.Administration一張好人卡,乖乖用ADSI或WMI解決問題。

我寫了一段工具函數,透過ADSI將整個IIS Meta資料轉成XDocument,之後便可用LINQ to XML進行各式複雜查詢,算是"雖不滿意但可接受"的簡便做法。

產生的XML長得像這樣:

為了延續.NET好威的傳統,程式照例要維持在100行以內解決。其實主要不過就是將DirectoryEntry的結構依樣轉換對照成XElement的結構,運用一下只應天上有的遞迴(To iterate is human, to recurse is divine. by L. Peter Deutsch,有人翻成"遞迴只應天上有、凡人該當用迴圈",妙哉!),三兩下就將整個IIS資料轉成XML文件囉! 因為轉資料過程有些漫長,我設計了一個回呼函數Action<string> progressCallback讓呼叫端可以持續收到處理過程的資訊,好讓使用者感受到電腦真的很忙,不是當掉了。

我在應用時,格外關心ASP.NET版本(有不少IIS上是ASP.NET 1.1與ASP.NET 2.0程式並存)以及網站所在目錄,因此擷取屬性資料時會額外判讀ASP.NET runtime版本,轉存為IIsWebVirtualDir XML元素的屬性,以方便查詢應用。(關於ASP.NET Runtime版本(v1.1, v2.0 or v4.0),IIS6用aspx註冊的ISAPI DLL路徑判斷,IIS7則要由依AppPool設定而定,不過針對IIS7我懶得安裝v1.1環境,所以程式中只區分v2.0及v4.0,未考慮v1.1,如果有朋友願意提供判斷邏輯,請留言。)

using System;
using System.Linq;
using System.Xml.Linq;
using System.DirectoryServices;
using System.Collections.Generic;
 
 
class IISDataHelper
{
    public static XDocument ReadSettings(string ip,
        string uid, string pwd, Action<string> progressCallback)
    {
        string path = String.Format("IIS://{0}/W3SVC", ip);
        DirectoryEntry w3svc =
            string.IsNullOrEmpty(uid) ?
            new DirectoryEntry(path) :
            new DirectoryEntry(path, uid, pwd);
        XDocument xd = XDocument.Parse("<root />");
        pools.Clear();
        exploreTree(w3svc, xd.Root, progressCallback);
        return xd;
    }
 
    static Dictionary<string, string> pools = 
        new Dictionary<string, string>();
 
    private static void exploreTree(
        DirectoryEntry de, XElement xe, Action<string> cb)
    {
        foreach (DirectoryEntry childEntry in de.Children)
        {
            XElement childElement = new XElement(
                childEntry.SchemaClassName,
                new XAttribute("Name", childEntry.Name),
                new XAttribute("Path", childEntry.Path)
                );
            //Get properties
            XElement propNode = new XElement("Properties");
            foreach (PropertyValueCollection pv in childEntry.Properties)
            {
                //Array
                if (pv.Value != null &&
                    pv.Value.GetType().IsArray)
                {
                    XElement propCol = new XElement(pv.PropertyName);
                    foreach (object obj in pv.Value as object[])
                    {
                        string v = Convert.ToString(obj);
                        //Set ASP.NET version
                        if (pools.Count == 0 && pv.PropertyName == "ScriptMaps"
                            && v.StartsWith(".aspx,"))
                        {
                            string aspNetVer =
                                v.Split(',')[1].Split('\\')
                                .Single(o => o.StartsWith("v"));
                            childElement.Add(
                                new XAttribute("AspNetVer", aspNetVer));
                        }
                        propCol.Add(new XElement("Entry", v));
                    }
                    propNode.Add(propCol);
                }
                else
                {
                    string v = Convert.ToString(pv.Value);
                    propNode.Add(new XElement(pv.PropertyName, v));
                //Set home directory
                if (pv.PropertyName == "Path")
                    childElement.Add(new XAttribute("HomeDir", v));
                //Try to find the runtime version
                else if (pools.Count > 0 && 
                    pv.PropertyName == "AppPoolId" && pools.ContainsKey(v))
                    childElement.Add(
                        new XAttribute("AspNetVer", pools[v]));
                }
            }
            //For IIS 7, use AppPool to decide ASP.NET runtime version
            if (childEntry.SchemaClassName == "IIsApplicationPool")
            {
                XElement runtimeVer = propNode.Element("ManagedRuntimeVersion");
                string ver = runtimeVer != null ? runtimeVer.Value : "v2.0";
                pools.Add(childEntry.Name, ver);
            }
            childElement.Add(propNode);
            
            xe.Add(childElement);
            if (cb != null) cb(childEntry.Name);
            exploreTree(childEntry, childElement, cb);
        }
    }
}

最後附上應用範例。以下程式會列出IIS上所有ASP.NET的版本、名稱及所在目錄:

using System;
using System.Linq;
using System.Xml.Linq;
 
namespace ListAspNetWebApp
{
    class Program
    {
        static void Main(string[] args)
        {
            XDocument xd =
                IISWebAppViewer.IISDataHelper.ReadSettings(
                    "192.168.1.101", @"domain\user", "password", 
                    (s) => 
                    {
                        //利用\r在同一列顯示目前處理進度
                        Console.Write("\rProcessing " + s.PadRight(50));
                    }
                );
            Console.WriteLine("\rDone!{0}", new String(' ', 30));
            var q = from o in xd.Root.Descendants()
                    where o.Attribute("HomeDir") != null &&
                          o.Attribute("AspNetVer") != null
                    select o;
            foreach (var o in q)
                Console.WriteLine(
                    "ASP.NET {0} Web [{1}] at {2}",
                    o.Attribute("AspNetVer").Value,
                    o.Attribute("Name").Value,
                    o.Attribute("HomeDir").Value);
            Console.Read();
        }
    }
}

結果如下,很方便吧!

Done!
ASP.NET v1.1.4322 Web [Root] at D:\wwwroot
ASP.NET v1.1.4322 Web [Scripts] at c:\inetpub\scripts
ASP.NET v1.1.4322 Web [IISHelp] at c:\winnt\help\iishelp
ASP.NET v1.1.4322 Web [IISAdmin] at C:\WINNT\System32\inetsrv\iisadmin
ASP.NET v1.1.4322 Web [IISSamples] at c:\inetpub\iissamples
ASP.NET v1.1.4322 Web [MSADC] at c:\program files\common files\system\msadc
ASP.NET v1.1.4322 Web [_vti_bin] at C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\40\isapi
ASP.NET v1.1.4322 Web [Printers] at C:\WINNT\web\printers


Comments

# by Amos

那請問一下,有辦法取得IIS站台上,所有client或某個站台的連線資訊,例如session id 、remote ip等資訊嗎?

# by Amolo

在運行此代碼前,本機要先有安裝IIS 6 Metabase Compatiblity,不然運行時會出現0x80005000的不明錯誤

# by Amolo

再補充下 if (childEntry.SchemaClassName == "IIsApplicationPool") 以上的判斷是IIS 7.0以上版本,如果要肩容IIS 6的話,需改寫為 if (childEntry.SchemaClassName == "IIsApplicationPool" || childEntry.SchemaClassName == "IIsApplicationPools") 這樣就可以抓到像是.NET 1.1的版本囉~

# by Jeffrey

to Amolo,感謝補充。一併補上 PowerShell 做法: http://blog.darkthread.net/post-2016-10-22-wmi-export-iis6-setting.aspx

# by Hung

小弟在Web上使此方法,並將網站放到主機IIS上,應用集區設為LocalSystem時可以讀取其他台主機上的IIS資訊,但是卻無法讀取網站本身主機的IIS(在VS開發環境確實可以讀到該主機資訊)。 程式連結寫法如下: DirectoryEntry iis = new DirectoryEntry("IIS://" + ServerName + "/w3svc", $"local_machineName + "."+ Domain\\Administrator", "password", AuthenticationTypes.Secure);

Post a comment