作成 2013.11.09
更新 2017.05.12
IIS 8.5 で Active Directory ユーザーアカウントのパスワード変更
IISADMPWD が今後新しいバージョンで動作保証しないそうなので作りました。
Active Directory のユーザーが自身のパスワードを変更できるようにします。
実際に運用する際は、別のページから iframe タグで呼び出すのが良いと思います。
なお、http でも動作しますがパスワードが平文で通過することから、https による運用を強くお勧めします。
動作環境
  1. Windows Server 2012 R2 + IIS 8.5
    役割サービスの IIS の項目の「ASP.NET 4.5」を有効にするだけで使用可能になります。
  2. Windows Server 2008 R2 + IIS 7.5
    事前に .Net Framework 4 をインストールした状態で、IIS の「ASP.NET」を有効にすると使用可能になります。
    もし、IIS インストール後に .Net Framework 4 をインストールした場合は、aspnet_regiis -i を実行することで使用可能になります。実行ファイルは C:\Windows\Microsoft.NET\Framework64\v4.0.30319 フォルダにあります。
コード
  • 10行目
    bool debugMode = false; の部分を true へ変更すると途中の値の状態が確認できます。
  • 77行目
    ユーザーアカウントの検索は sAMAccountName と cn のOR条件となっています。そのため Active Directory に日本語ユーザー名で登録している場合は、名前 + パスワードでも通過します。ログオンIDのみとしたい場合やメールアドレスで認証したい場合はこの行を書き換えてください。
  • 107行目
    ユーザーのパスワードが有効期限切れになっている場合、もしくは「ユーザーは次回ログオン時にパスワード変更が必要」がオンになっている場合は、認証失敗しパスワードを変更できません。パスワードを変更できるようにするには、この行を管理者アカウントとパスワードに差し替えます。
  • その他、web.config の作成や書き換えは不要です。
pwdchange.aspx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
<%@ PAGE LANGUAGE="C#" %>
<%@ Assembly Name="System.DirectoryServices, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" %>
<%@ Assembly Name="System.DirectoryServices.AccountManagement, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" %>
<%@ Import Namespace="System.DirectoryServices" %>
<%@ Import Namespace="System.DirectoryServices.AccountManagement" %>
<html>
<head>
<title>AD User Change Password</title>
<script runat="server">
bool debugMode = false;

void Page_Load(object sender, EventArgs e)
{
  if(debugMode){ lbl_debug.Text = "Debug Mode: ON"; }
  old_password.TextMode = TextBoxMode.Password;
  new_password.TextMode = TextBoxMode.Password;
  new_password2.TextMode = TextBoxMode.Password;
}
void password_change(object sender, EventArgs e)
{
  String ldapBaseDN = "";
  String dNSDomainName = "";
  String nTDomainName = "";
  String samName = "";
  String userName = user_name.Text;
  String searchText = "";
  String ldapUserPath = "";

  lbl_change_result.Text = "";
  lbl_system_result.Text = "";
  lbl_base_dn.Text = "";
  lbl_dns_domain.Text = "";
  lbl_nt_domain.Text = "";
  lbl_user_path.Text = "";
  lbl_sam.Text = "";

  if(userName.Length == 0){
    lbl_system_result.Text = "ユーザー名が指定されていません。";
    return;
  }
  if(String.Compare(new_password.Text, new_password2.Text) != 0){
    lbl_system_result.Text = "新しいパスワードが一致していません。";
    return;
  }

  try
  {
    DirectoryEntry domain = new DirectoryEntry("LDAP://RootDSE");
    ldapBaseDN = domain.Properties["defaultNamingContext"][0].ToString();
    if(debugMode){ lbl_base_dn.Text = " LDAP Root DN: " + ldapBaseDN; }
  }
  catch(Exception ex1)
  {
    if(debugMode){
      lbl_system_result.Text = ex1.Message;
    }else{
      lbl_system_result.Text = "このサーバーはドメインに参加していないようです。";
    }
    return;
  }
  System.Text.RegularExpressions.Regex r = new System.Text.RegularExpressions.Regex("=([^,]+),?");
  System.Text.RegularExpressions.Match m = r.Match(ldapBaseDN);
  if (m.Success)
  {
    dNSDomainName = m.Groups[1].ToString();
    m = m.NextMatch();
    while (m.Success)
    {
      dNSDomainName = dNSDomainName + "." + m.Groups[1].ToString();
      m = m.NextMatch();
    }
  }
  if(debugMode){ lbl_dns_domain.Text = " DNS Domain: " + dNSDomainName; }
  nTDomainName = Environment.GetEnvironmentVariable("USERDOMAIN");
  if(debugMode){ lbl_nt_domain.Text = " NT Domain: " + nTDomainName; }

  searchText = "(&(objectClass=user)(!objectClass=computer)(|(sAMAccountName=" + userName + ")(cn=" + userName + ")))";
  try
  {
    using (DirectorySearcher ds = new DirectorySearcher(searchText))
    {
      SearchResult sr = ds.FindOne();
      if(sr == null)
      {
        if(debugMode){ lbl_system_result.Text = "ユーザー名が見つかりません。"; }
        else { lbl_system_result.Text = "ユーザー名またはパスワードが違います。"; }
        return;
      }
      else
      {
        ldapUserPath = sr.Path;
        if(debugMode){ lbl_user_path.Text = " LDAP User Path: " + ldapUserPath; }
        samName = sr.Properties["sAMAccountName"][0].ToString();
        if(debugMode){ lbl_sam.Text = " sAMAccountName: " + samName; }
      }
    }
  }
  catch(ArgumentException ae)
  {
    lbl_system_result.Text = ae.Message;
    return;
  }

  try
  {
    using(PrincipalContext ctx = new PrincipalContext(ContextType.Domain,
      dNSDomainName, nTDomainName + @"\" + samName, old_password.Text))
    using(UserPrincipal usr = UserPrincipal.FindByIdentity(ctx, samName))
    {
      usr.ChangePassword(old_password.Text, new_password.Text);
      if(debugMode){ lbl_change_result.Text = "パスワードを変更しました。"; }
      else{
        Response.ClearContent();
        Response.Write("<html><head></head><body>パスワードを変更しました。</body></html>");
        Response.Flush();
        Response.Close();
      }
    }
  }
  catch(PasswordException pe)
  {
    if(debugMode){ lbl_system_result.Text = pe.Message; }
    else { lbl_system_result.Text = "ユーザー名またはパスワードが違います。"; }
    return;
  }
  catch(Exception ex2)
  {
    if(debugMode){ lbl_system_result.Text = ex2.Message; }
    else { lbl_system_result.Text = "ユーザー名またはパスワードが違います。"; }
    return;
  }
}
</script>
</head>
<body>
  <form runat="server">
  <table>
  <tr><td>ユーザー名</td><td><asp:TextBox id="user_name" Columns="50" Rows="1" Text="" runat="server" /></td></tr>
  <tr><td>現在のパスワード</td><td><asp:TextBox id="old_password" Columns="50" Rows="1" Text="" runat="server" /></td></tr>
  <tr><td>新しいパスワード</td><td><asp:TextBox id="new_password" Columns="50" Rows="1" Text="" runat="server" /></td></tr>
  <tr><td>確認入力</td><td><asp:TextBox id="new_password2" Columns="50" Rows="1" Text="" runat="server" /></td></tr>
  </table>
  <asp:Button id="btn_change" Text="Change" OnClick="password_change" runat="server" />
  <asp:Label style="color:#080;font-weight:bold;margin-left:1em" id="lbl_change_result" Text="" runat="server" />
  <asp:Label style="color:#f00;font-weight:bold;margin-left:1em" id="lbl_system_result" Text="" runat="server" />
  <!-- ここから下はデバッグ情報 -->
  <asp:Label id="lbl_debug" Text="" runat="server" />
  <asp:Label id="lbl_base_dn" Text="" runat="server" />
  <asp:Label id="lbl_nt_domain" Text="" runat="server" />
  <asp:Label id="lbl_dns_domain" Text="" runat="server" />
  <asp:Label id="lbl_user_path" Text="" runat="server" />
  <asp:Label id="lbl_sam" Text="" runat="server" />
  </form>
</body>
</html>
編集例
ログオンIDのみ認証可能にする
変更前 77行目
1
  searchText = "(&(objectClass=user)(!objectClass=computer)(|(sAMAccountName=" + userName + ")(cn=" + userName + ")))";
変更後 77行目
1
  searchText = "(&(objectClass=user)(!objectClass=computer)(sAMAccountName=" + userName + "))";
パスワードの有効期限切れでも変更可能にする
パスワードの有効期限切れまたは「ユーザーは次回ログオン時にパスワード変更が必要」がオンの場合でも変更可能になります。
変更後は、「ユーザーは次回ログオン時にパスワード変更が必要」はオフに変わります。
変更前 106-107行目
1
2
    using(PrincipalContext ctx = new PrincipalContext(ContextType.Domain,
      dNSDomainName, nTDomainName + @"\" + samName, old_password.Text))
変更後 106-107行目
1
2
    using(PrincipalContext ctx = new PrincipalContext(ContextType.Domain,
      dNSDomainName, nTDomainName + @"\Administrator", @"管理者パスワード"))
管理者パスワードを外部ファイルに保存する場合
変更可能なOUを限定する
ds.SearchRoot の行を追加します。
変更前 80-83行目
1
2
3
4
    using (DirectorySearcher ds = new DirectorySearcher(searchText))
    {
      SearchResult sr = ds.FindOne();
      if(sr == null)
変更後 80-84行目
1
2
3
4
5
    using (DirectorySearcher ds = new DirectorySearcher(searchText))
    {
      ds.SearchRoot = new DirectoryEntry(@"LDAP://OU=testuser,DC=test,DC=lan");
      SearchResult sr = ds.FindOne();
      if(sr == null)
SearchRoot のパスを調べるには以下のコマンドをドメイン コントローラーで実行します。
1
2
C:\> dsquery ou -name testuser
OU=testuser,DC=test,DC=lan

©2004-2017 UPKEN IPv4