The Wiert Corner – irregular stream of stuff

Jeroen W. Pluimers on .NET, C#, Delphi, databases, and personal interests

  • My work

  • My badges

  • Twitter Updates

  • My Flickr Stream

    20140508-Delphi-2007--Project-Options--Cannot-Edit-Application-Title-HelpFile-Icon-Theming

    20140430-Fiddler-Filter-Actions-Button-Run-Filterset-now

    20140424-Windows-7-free-disk-space

    More Photos
  • Pages

  • All categories

  • Enter your email address to subscribe to this blog and receive notifications of new posts by email.

    Join 1,358 other followers

C# / .NET – setting or clearing the Password Never Expires flag for a user

Posted by jpluimers on 2009/10/11

Recently, I had to change the “Password Never Expires” flag for some users on Windows systems.

In the past, there used to be a netuser tool available from WindowsITPro where you could use the pwnexp flag to set or clear that flag.
That tool seems to be vanished, so I was searching for alternatives.

Most alternatives I found depend on some  kind of scripting, or the use of the WMIC WMI command line interface: that was “out” because this particular setup is running on Windows XP Embedded, which is trimmed down very much.
The only C# example I found was on CodeProject, but it does

  • not take into account the existing flags correctly,
  • have  hard coded literals without any references where they are from,
  • use bit flag arithmetic without letting the C# compiler do its magic with enums,
  • use a call to the deprecated  InvokeGet method,
  • use the Invoke(“Put”, … way of calling that so many people use (which actually should have been an – also deprecated – InvokeSet method),
  • use COM Interop

Hence the solution below:
C#, with the proper ADS_USER_FLAG_ENUM enum from the MSDN documentation and no COM Interop, it also moves all literals to constants.

This example does not use any Interop, but it

With those, it becomes more readable, easier to debug and therefore a lot more maintainable.

Note that this code needs full trust security.
In my case that is not a problem, as this code executes while building the Windows XP Embedded image.

        // see http://msdn.microsoft.com/en-us/library/aa772300(VS.85).aspx
        [Flags]
        enum ADS_USER_FLAG_ENUM
        {
            ADS_UF_SCRIPT = 1,         // 0x1
            ADS_UF_ACCOUNTDISABLE = 2,         // 0x2
            ADS_UF_HOMEDIR_REQUIRED = 8,         // 0x8
            ADS_UF_LOCKOUT = 16,        // 0x10
            ADS_UF_PASSWD_NOTREQD = 32,        // 0x20
            ADS_UF_PASSWD_CANT_CHANGE = 64,        // 0x40
            ADS_UF_ENCRYPTED_TEXT_PASSWORD_ALLOWED = 128,       // 0x80
            ADS_UF_TEMP_DUPLICATE_ACCOUNT = 256,       // 0x100
            ADS_UF_NORMAL_ACCOUNT = 512,       // 0x200
            ADS_UF_INTERDOMAIN_TRUST_ACCOUNT = 2048,      // 0x800
            ADS_UF_WORKSTATION_TRUST_ACCOUNT = 4096,      // 0x1000
            ADS_UF_SERVER_TRUST_ACCOUNT = 8192,      // 0x2000
            ADS_UF_DONT_EXPIRE_PASSWD = 65536,     // 0x10000
            ADS_UF_MNS_LOGON_ACCOUNT = 131072,    // 0x20000
            ADS_UF_SMARTCARD_REQUIRED = 262144,    // 0x40000
            ADS_UF_TRUSTED_FOR_DELEGATION = 524288,    // 0x80000
            ADS_UF_NOT_DELEGATED = 1048576,   // 0x100000
            ADS_UF_USE_DES_KEY_ONLY = 2097152,   // 0x200000
            ADS_UF_DONT_REQUIRE_PREAUTH = 4194304,   // 0x400000
            ADS_UF_PASSWORD_EXPIRED = 8388608,   // 0x800000
            ADS_UF_TRUSTED_TO_AUTHENTICATE_FOR_DELEGATION = 16777216   // 0x1000000
        } ;

        /// <summary>
        /// Sets or clears the "Password Never Expires" flag for a user on the current system.
        /// This is based on ideas in http://www.codeproject.com/KB/system/OSUserMangement.aspx,
        /// but not that the code there is wrong: it does not take into account the current userFlags value
        /// The code below does take that into account and is much more maintainable.
        /// 
        /// the logic can be made a bit easier by applying sets
        /// </summary>
        /// <param name="userName">name of the user to set the Password Never Expires flag</param>
        /// <param name="passwordNeverExpires">new flag value</param>
        protected virtual void setPasswordNeverExpires(string userName, bool passwordNeverExpires)
        {
            const string userNameString = "userName";
            const string userFlagsString = "userFlags";

            string machineName = Environment.MachineName;

            DirectoryEntry userInThisComputerDirectoryEntry = getUserInThisComputerDirectoryEntry(machineName, userName);
            if (null == userInThisComputerDirectoryEntry)
                throw new ArgumentException("not found in " + machineName, userNameString);

            PropertyValueCollection userFlagsProperties = userInThisComputerDirectoryEntry.Properties[userFlagsString];

            ADS_USER_FLAG_ENUM userFlags = (ADS_USER_FLAG_ENUM)(userFlagsProperties.Value);
            ADS_USER_FLAG_ENUM newUserFlags = userFlags;

            if (passwordNeverExpires)
                newUserFlags = newUserFlags | ADS_USER_FLAG_ENUM.ADS_UF_DONT_EXPIRE_PASSWD;
            else
                newUserFlags = newUserFlags & (~ADS_USER_FLAG_ENUM.ADS_UF_DONT_EXPIRE_PASSWD);

            userFlagsProperties.Value = newUserFlags;

            userInThisComputerDirectoryEntry.CommitChanges();
        }

        protected virtual DirectoryEntry getUserInThisComputerDirectoryEntry(string machineName, string userName)
        {
            DirectoryEntry computerDirectoryEntry = getComputerDirectoryEntry(machineName);

            const string userSchemaClassName = "user";
            return computerDirectoryEntry.Children.Find(userName, userSchemaClassName);
        }

        protected virtual DirectoryEntry getComputerDirectoryEntry(string machineName)
        {
            //Initiate DirectoryEntry Class To Connect Through WINNT Protocol
            // see: http://msdn.microsoft.com/en-us/library/system.directoryservices.directoryentry.path.aspx
            const string pathUsingWinNTComputerMask = "WinNT://{0},computer";
            string path = string.Format(pathUsingWinNTComputerMask, machineName);
            DirectoryEntry thisComputerDirectoryEntry = new DirectoryEntry(path);
            return thisComputerDirectoryEntry;
        }

This code is just an example on how you can do it in a maintainable way.
It can easily be extended to other properties as well.

–jeroen

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

 
Follow

Get every new post delivered to your Inbox.

Join 1,358 other followers

%d bloggers like this: