Friday, July 11, 2008

Password expiration email utility

I had trouble finding a free utility that would send employees emails before their windows passwords were ready to expire, so I wrote a C# console application that does it. Here is the source code:



using System;
using System.Collections.Generic;
using System.Text;
using System.DirectoryServices;
using System.DirectoryServices.ActiveDirectory;
using System.Configuration;
using ActiveDs;
using System.Collections;
using System.IO;
using System.Net.Mail;


namespace PasswordNotifcation
{
class Program
{
const int ADS_SCRIPT = 0x0001;
const int ADS_ACCOUNTDISABLE = 0x0002;
const int ADS_HOMEDIR_REQUIRED = 0x0008;
const int ADS_LOCKOUT = 0x0010;
const int ADS_PASSWD_NOTREQD = 0x0020;
const int ADS_PASSWD_CANT_CHANGE = 0x0040;
const int ADS_ENCRYPTED_TEXT_PWD_ALLOWED = 0x0080;
const int ADS_TEMP_DUPLICATE_ACCOUNT = 0x0100;
const int ADS_NORMAL_ACCOUNT = 0x0200;
const int ADS_INTERDOMAIN_TRUST_ACCOUNT = 0x0800;
const int ADS_WORKSTATION_TRUST_ACCOUNT = 0x1000;
const int ADS_SERVER_TRUST_ACCOUNT = 0x2000;
const int ADS_DONT_EXPIRE_PASSWORD = 0x10000;
const int ADS_MNS_LOGON_ACCOUNT = 0x20000;
const int ADS_SMARTCARD_REQUIRED = 0x40000;
const int ADS_TRUSTED_FOR_DELEGATION = 0x80000;
const int ADS_NOT_DELEGATED = 0x100000;
const int ADS_USE_DES_KEY_ONLY = 0x200000;
const int ADS_DONT_REQ_PREAUTH = 0x400000;
const int ADS_PASSWORD_EXPIRED = 0x800000;
const int ADS_TRUSTED_TO_AUTH_FOR_DELEGATION = 0x1000000;


public static void SendMailMessage(string ToAddress,string FromAddress, string MessageSubject, string MessageText, string ToCC, string ToBCC, string MailServer)
{
System.Net.Mail.MailMessage mail = new System.Net.Mail.MailMessage(FromAddress, ToAddress, MessageSubject, MessageText);

if(ToCC != string.Empty && ToCC != null)
{
mail.CC.Add(ToCC);
}
if(ToBCC != string.Empty && ToBCC != null)
{
mail.Bcc.Add(ToBCC);
}

System.Net.Mail.SmtpClient cli = new System.Net.Mail.SmtpClient();
cli.Host = MailServer;
cli.Send(mail);
}



static void Main(string[] args)
{
if (ArgFound("-?", args))
{
Console.WriteLine(@"PasswordNotification.exe - Sends emails to any users that have passwords that are ready to expire.");
Console.WriteLine(@"Command Line Example");
Console.WriteLine(@"PasswordNotification.exe");
Console.WriteLine(@"");
Console.WriteLine(@"This utility requires two files. 1) a config file called Settings.config that contains application settings, and 2)a set of email template files that conain the warning text of each email.");
return;

}

try
{


System.Xml.XmlDocument doc = new System.Xml.XmlDocument();

doc.Load(AppDomain.CurrentDomain.SetupInformation.ApplicationBase + @"\settings.config");

System.Xml.XmlNodeList nl = doc.SelectNodes("//Warning");



System.Xml.XmlNode nodtmp = doc.SelectSingleNode("//StartingOU");
string strStartingOU = nodtmp.Attributes["Value"].Value;


nodtmp = doc.SelectSingleNode("//SmtpServer");
string strSmtpServer = nodtmp.Attributes["Value"].Value;

nodtmp = doc.SelectSingleNode("//MailFrom");
string strEmailFrom = nodtmp.Attributes["Value"].Value;

TimeSpan tsMaxPasswordAge = GetMaxPasswordAge();
int iMaxPwdAge = tsMaxPasswordAge.Days;

DirectoryEntry objDirEnt = new DirectoryEntry(strStartingOU);

DirectorySearcher ds = new DirectorySearcher(objDirEnt);
SearchResultCollection src = null;

ds.Filter = "(objectClass=user)";

src = ds.FindAll();
if (src.Count > 0)
{
foreach (SearchResult sr in src)
{
DirectoryEntry UserEntry = sr.GetDirectoryEntry();



WriteLogMessage("Processing:" + UserEntry.Name);

WriteLogMessage("Max Password Age:" + iMaxPwdAge);

//Console.WriteLine("Account control:" + UserEntry.Properties["userAccountcontrol"].Value);

int userAccountControl = Convert.ToInt32(UserEntry.Properties["userAccountcontrol"].Value);
if ((userAccountControl & ADS_DONT_EXPIRE_PASSWORD) > 0)
{
WriteLogMessage("Password never expires for:" + UserEntry.Name);
}
else
{
WriteLogMessage("Password expiration date found for:" + UserEntry.Name);

ActiveDs.IADsUser native = (ActiveDs.IADsUser)UserEntry.NativeObject;

DateTime passwordLastChanged = new DateTime(9999, 1, 1);
try
{

passwordLastChanged = native.PasswordLastChanged;


}

catch { }
string strEmailTo = "";
try
{
strEmailTo = native.EmailAddress;
}
catch { };

WriteLogMessage("Password last changed date:" + passwordLastChanged.ToString());

if (passwordLastChanged.Year != 9999)
{
DateTime expireDate = passwordLastChanged.AddDays(iMaxPwdAge);

TimeSpan ts = expireDate - DateTime.Now;

int iDaysTilExpired = ts.Days;

WriteLogMessage("Days til password expires:" + iDaysTilExpired);


foreach (System.Xml.XmlNode nod in nl)
{
int iDays = Convert.ToInt32(nod.Attributes["Days"].Value);


if (iDays == iDaysTilExpired)
{

if (strEmailTo != "")
{

string strSubject = nod.Attributes["Subject"].Value;
string strTemplatePath = nod.Attributes["Template"].Value;


using (TextReader streamReader = new StreamReader(strTemplatePath))
{
string strContents = streamReader.ReadToEnd();

strContents = strContents.Replace("%%days%%", iDays.ToString());

Console.WriteLine("Sending email to:" + strEmailTo);

SendMailMessage(strEmailTo, strEmailFrom, strSubject, strContents, "", "", strSmtpServer);
WriteLogMessage("Email sent to:" + strEmailTo);

}
}
else
{
WriteLogMessage("Email address is blank so don't send");
}

}
}
}
else
{
WriteLogMessage("Password last changed date is not set.");

}
}
}
}
}
catch (Exception ex)
{
WriteLogMessage("Error -- Exception thrown:" + ex.ToString());
}

}

static public bool ArgFound(string strArg, string[] args)
{
foreach (string strTempArg in args)
{
if (strTempArg.ToLower().StartsWith(strArg.ToLower()))
{
return true;
}
}
return false;

}

static private void WriteLogMessage(string Message)
{
Console.WriteLine(Message);
System.IO.StreamWriter sw = new System.IO.StreamWriter(@"log.txt", true);
DateTime dtTimestamp = System.DateTime.Now;

string strLine = dtTimestamp.Year + "-" + dtTimestamp.Month + "-" + dtTimestamp.Day + " " + dtTimestamp.Hour + ":" + dtTimestamp.Minute + ":" + dtTimestamp.Second + "." + dtTimestamp.Millisecond + "\t";
strLine += Message;

sw.WriteLine(strLine);
sw.Flush();
sw.Close();

}


public static TimeSpan GetMaxPasswordAge()
{
using (Domain d = Domain.GetCurrentDomain())
using (DirectoryEntry domain = d.GetDirectoryEntry())
{
DirectorySearcher ds = new DirectorySearcher(
domain,
"(objectClass=*)",
null,
SearchScope.Base
);
SearchResult sr = ds.FindOne();
TimeSpan maxPwdAge = TimeSpan.MinValue;
if (sr.Properties.Contains("maxPwdAge"))
maxPwdAge = TimeSpan.FromTicks((long)sr.Properties["maxPwdAge"][0]);
return maxPwdAge.Duration();
}
}

}
}






The code also requires a config file called settings.config that looks like this. You will need to change it to match the Active directory OU where you want the utility to start searching for users, or you can change it to the root OU. You can add as many "warning" sections as you wish for different notification schemes. The example below will send a warning 10 days, 3 days, and 1 day before a password is ready to expire. You will also need to set up the settings for your SMTP server to send the email.




<?xml version="1.0" encoding="utf-8" ?>
<Settings>

<StartingOU Value="LDAP://123.123.123.12/ou=employees,dc=mydomain,dc=com"></StartingOU>
<SmtpServer Value="mail.mydomain.com"></SmtpServer>
<MailFrom Value="System@mydomain.com"></MailFrom>
<Warning Days="1" Subject="Your password will expire soon" Template="Template1.txt"></Warning>
<Warning Days="3" Subject="Your password will expire soon" Template="Template1.txt"></Warning>
<Warning Days="10" Subject="Your password will expire soon" Template="Template1.txt"></Warning>

</Settings>








The config file above also references a templatefile called "Template1.txt" for the text of your email. The template should look something like this:





Your windows password will expire in %%days%% days.

Please go to http://123.123.123.12/EmployeeTools to reset it.

Thank You





Set up your console application as a scheduled task that runs every day. Set up the scheduled task to have read access to the active directory container for your users.

Wednesday, January 9, 2008

Installing a new circuit

I recently installed a new 20 Amp Circuit for an "over the range" microwave in my house. Most of it was pretty simple. I had to buy a 20 Amp receptacle, some 12 gauge electrical wire (at lowes it's a yellow color), a new 20 amp circuit breaker, a wire clamp for the breaker box, and that's about it.

I used a Black and Decker book on electrical wiring which was really helpful. I only ran into one problem -- When I tried to turn off the main breaker for the entire house, it would flip back to the "on" position and the power would not go off. Eventually I realized that you have to use a lot of force to shut off the main breaker switch -- I guess because it's so big, it has a large spring inside.

When I had the panel open I also noticed a crackling noise coming from one of the breakers. I eventually realized that one of the older breakers was not tightly snapped in. After pushing it, it snapped into place and the crackling stopped.

This whole project took me about 4 hours including fishing the wire, cutting a hole for the receptacle, and hooking everything up.