【潛盾機】列出IIS上所有ASP.NET網站應用程式
5 |
實務上常有多個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);