工作機平日使用本機帳號登入,存取伺服器或網站時才使用網域(AD)帳號,為了避免每次都重新輸入,我會在第一次登入時使用Windows內建的密碼記憶功能把網域帳號密碼存起來。公司的伺服器及網站眾多,於是乎就出現一張長長的密碼記憶清單...

問題來了! 基於資安管控,網域帳號的密碼需要定期更換,但換過密碼後要是忘了更新上述的記憶密碼,下回想連上某台伺服器,Windows便會拿著舊密碼試著登入... 登楞! 很快地,連錯三次,帳號上鎖 orz 必須連絡網管才能解鎖。

因此,每回改完網域帳號密碼,得趕快更新記憶密碼。要像下圖逐一點開每台伺服器/網站項目,按下【Edit】修改密碼、儲存,同樣動作要重覆十多次,而且每三個月需要重頭演練一次。雖然對正常人來說,操作十來次根本不算什麼,但,動作重複是程式設計功力不足的象徵,對程式魔人是一種羞辱。不行! 重複操作正在毁滅我的人生(謎之聲: 有這麼嚴重? 施主,你病得不輕呀!),如果沒法讓它自動化,我就退出江湖。

認真研究後,找到Windows有個內建命令列工具,cmdkey,可用來管理記憶密碼。

既然有工具,理論上就有對應的Windows API,但已有現成cmdkey.exe可用,透過Process呼叫外部程式的把戲也難不倒我,就不花功夫去研究API了,寫支C#程式,先輸入網域帳號(Domain\Account)及新密碼,呼叫cmdkey /list列出所有儲存密碼,從中比對User名稱找出屬於該帳號的項目,再逐一呼叫cmdkey /add:server_name /user:Domain\Account /pass:newPassword變更成新密碼,就大功告成囉!

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
 
namespace UpdateStoredPwd
{
    class Program
    {
 
        static void Main(string[] args)
        {
            Regex reTarget = new Regex("Target: (?<t>.+):target=(?<s>.+)");
            Regex reUser = new Regex("User: (?<u>.+)");
            Console.Write("User: ");
            string account = Console.ReadLine();
            Console.Write("Password: ");
            Console.ForegroundColor = ConsoleColor.Black;
            string pwd = Console.ReadLine();
            Console.ForegroundColor = ConsoleColor.White;
 
            bool catchAccount = false;
            string currTarget = null, currType = null;
            foreach (string line in Execute("cmdkey", "/list")
                                    .Replace("\r", string.Empty).Split('\n'))
            {
                if (!catchAccount)
                {
                    var m = reTarget.Match(line);
                    if (m.Success)
                    {
                        catchAccount = true;
                        currTarget = m.Groups["s"].Value;
                        currType = m.Groups["t"].Value;
                    }
                }
                else
                {
                    var m = reUser.Match(line);
                    if (m.Success)
                    {
                        catchAccount = false;
                        string user = m.Groups["u"].Value;
                        if (string.Compare(user, account, true) == 0)
                        {
                            string a = string.Format(
                                "/{3}:{0} /user:{1} /pass:{2}", currTarget, account, 
                                pwd, currType == "Domain" ? "add" : "generic");
                            Console.WriteLine("cmdkey " + 
                                a.Replace("pass:" + pwd, "pass:*****"));
                            string res = Program.Execute("cmdkey", a);
                            Console.WriteLine(res);
                        }
                    }
                    
                }
 
            }
            Console.Read();
        }
 
        static string Execute(string prog, string args)
        {
            Process p = new Process();
            p.StartInfo = new ProcessStartInfo(prog, args);
            //必須要設定以下兩個屬性才可將輸出結果導向
    p.StartInfo.UseShellExecute = false;
            p.StartInfo.RedirectStandardOutput = true;
            //不顯示任何視窗
              p.StartInfo.CreateNoWindow = true;
            p.Start();
            StringBuilder sb = new StringBuilder();
            while (!p.HasExited)
            {
                string line = p.StandardOutput.ReadLine();
                sb.AppendLine(line);
            } 
            return sb.ToString();
        }
    }
}

【後記】

猜想已有軟體能做類似的批次密碼修改,但初步搜尋沒找到,就決定把尋找時間省下來自寫兼練功(謎: 明明就是手癢所以不認真找),但若有人知道有相關軟體可用,歡迎分享。另外,也找到PowerShell呼叫API更改記憶密碼的範例,但寫慣C#硬要用PowerShell有種舒馬克跑去開飛機的彆腳感,最後還是抄起C#就打,呵!


Comments

# by Rico

推舒米開飛機~

Post a comment