前一篇文章成功將 IIS 6 網站設定匯出成 JSON,不過原始資料太過龐雜,每筆虛擬目錄屬性超過140條,讓人眼花瞭亂。事實上因 IIS 設定具有繼承性,父目錄與子目錄的屬性絕大部分是相同的,針對某個虛擬目錄做的額外設定才是觀注焦點。例如:掛在可匿名存取 P 目錄下的 C 目錄被設成整合式驗證,描述 C 目錄設定時時只要列出 AuthNTLM = true 就好,與 P 目錄相同的設定可以全部省略。為實現這點,我想到一個簡單有效的演算法:拿子目錄的所有屬性跟父目錄比較,只顯示有差異部分。
WMI 匯出的 IISWebVirtualDirSetting 資料為 JSON 格式,轉為強型別處理起來才順手。講到這個不得不推 Visual Studio 強大的 Paste JSON As Classes 功能:選取 IISWebVirtualDirSetting 匯出的全部 JSON 內容,點選「Edit / Paste Special / Paste JSON As Classes」:
見證奇蹟的時刻… Visual Studio 自動依 JSON 資料產生對應型別,連 HttpCustomHeader 這種物件陣列屬性,陣列元素物件也被轉成強型別!
將 public class Class1 更名並轉為部分宣告 public partial class VirDirSetting,我另外再補上額外屬性及方法方便後續處理:
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
namespace WmiDataAnalyzer
{
publicpartialclass VirDirSetting
{
//所屬子網站
public List<VirDirSetting> Children = new List<VirDirSetting>();
//父網站名稱
publicstring ParentName;
//父網站物件
public VirDirSetting Parent;
//所有可能的父網站名稱
publicstring[] AncestorNames
{
get
{
var p = this.Name.Split('/');
return Enumerable.Range(1, p.Length - 1)
.Select(i => string.Join("/", p.Take(i).ToArray())).ToArray();
}
}
//層級深度
publicint Level
{
get { returnthis.Name.Split('/').Length - 2; }
}
//動態比對屬性用屬性集合
static Dictionary<string, PropertyInfo> Properties
{
get
{
returntypeof(VirDirSetting)
.GetProperties(BindingFlags.Public | BindingFlags.Instance)
.Where(o => !"AncestorNames,Level,ScriptMaps".Split(',').Contains(o.Name))
.ToDictionary(o => o.Name, o => o);
}
}
//與Parent比較,找出有差異的設定
public Dictionary<string, string> GetExplicitSettings()
{
var diff = new Dictionary<string, string>();
if (this.Parent == null)
{
diff.Add("Remark", "**Root**");
}
else
{
foreach (var p in VirDirSetting.Properties.Values)
{
var pv = JsonConvert.SerializeObject(p.GetValue(this.Parent));
var cv = JsonConvert.SerializeObject(p.GetValue(this));
if (pv.CompareTo(cv) != 0)
diff.Add(p.Name, cv);
}
}
return diff;
}
}
}
寫一小段程式將 JSON 反序列化為物件集合,並找出彼此從屬關係,再印出網站結構及差異設定:
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace WmiDataAnalyzer
{
class Program
{
staticvoid Main(string[] args)
{
Dictionary<string, VirDirSetting> data =
JsonConvert.DeserializeObject<VirDirSetting[]>(
File.ReadAllText("Sample.json")).ToDictionary(o => o.Name, o => o);
var roots = new List<VirDirSetting>();
//建立從屬關係
foreach (var vds in data.Values)
{
foreach (var anc in vds.AncestorNames)
{
if (data.ContainsKey(anc))
{
vds.ParentName = anc;
vds.Parent = data[anc];
vds.Parent.Children.Add(vds);
break;
}
}
if (string.IsNullOrEmpty(vds.ParentName))
{
roots.Add(vds);
}
}
//印出從屬關係
foreach (var root in roots)
{
DumpStructure(root);
}
Console.Read();
}
staticvoid DumpStructure(VirDirSetting vds)
{
Console.WriteLine("{0}[{1}]",
newstring(' ', vds.Level * 4), vds.Name);
foreach (var kv in vds.GetExplicitSettings())
Console.WriteLine("{2} *{0}:{1}", kv.Key, kv.Value,
newstring(' ', vds.Level * 4 ));
foreach (var child in vds.Children)
{
DumpStructure(child);
}
}
}
}
我弄了一個測試網站結構如下,建立多個虛擬目錄並故意加入設定差異,例如:驗證方式、IP限制、不同ApplicationPool… 等等。
產生結果如下:
有少部分設定細節未包含在 IISWebVirtualDirSetting 中,要藉由其他 WMI 資料才能拼湊出完整設定。例如:虛擬目錄是否為網站應用程式可由 IISWebVirtualDir "AppRoot": null 與否判斷;IP 限制則要查詢 IISIpSecuritySetting,限定 IP 時會得到如下結果:
{
"Caption": null,
"Description": null,
"DomainDeny": [],
"DomainGrant": [],
"GrantByDefault": false,
"IPDeny": [],
"IPGrant": [
"127.0.0.1, 255.255.255.255",
"192.168.1.78, 255.255.255.255"
],
"Name": "W3SVC/1/ROOT/ParentWebApp/UNCVirtualDir",
"SettingID": null
},
由這些 WMI 資料,我們就能描繪現有網站的結構,進行檢視調整,並做為撰寫設定 IIS 網站自動腳本的依據。