Share via


Creating a Password Settings object using Powershell 1.0

Password Settings objects were introduced in Windows 2008 AD to allow administrators more control over the password policies for the domain.  These objects can be associated with a container of user objects or groups.

The following link provides details on how PSOs are applied:

msdn.microsoft.com/en-us/library/cc212097(PROT.10).aspx

When you create a PSO, there are 10 properties that must be set:

msds-maximumpasswordage - a 64 bit time value interval represented in nanoseconds before the password expires.
msds-minimumpasswordage  -  a 64 bit time value interval represented in nanoseconds before a password change is allowed.
msds-LockoutObservationWindow -a 64 bit time value interval in nanoseconds to look for possible bad password entries
msds-LockoutDuration - a 64 bit time value interval in nanoseconds to lock the account after a specified number of bad password attempts during the observation period.
msds-LockoutThreshold - a 16-bit unsigned integer indicating the number of bad password attempts within an Effective-LockoutObservationWindow that will cause an account to be locked out.
msds-PasswordSettingsPrecedence - an integer value used to  resolve conflicts with multiple PSOs.  The PSO with the lowest precedence value is applied.
msds-PasswordHistoryLength-  number of old passwords that are retained to prevent duplication of a previous password.
msds-MinimumPasswordLength - minim length of the password.
msDS-PasswordComplexityEnabled - Boolean value, if true, password complexity rules apply to new passwords.
msDS-PasswordReversibleEncryptionEnabled -  A Boolean value indicating that the user's clear text password is to be stored in the supplemental Credentials attribute.

Most of these properties are straight forward types and can be easily converted from native Powershell types.  The time intervals are represented as 64 bit negative values for the number of nanoseconds each property should be in effect.  The following KB provides some useful information on how to create these values using long value types:
support.microsoft.com/kb/954414

There is a native .Net type that works with nanosecond intervals called System.TimeSpan.  If you use the static Parse method, you can provide a string representation for the time interval using the following format:

"Days.hours:Minutes:Seconds:Milliseconds"

So, if you wanted to create a TimeSpan object for time interval representing 2 days, 3 hours, 15 minutes, 30 seconds, the string would look like: "3.03:15:30".  To create a TimeSpan object in PSH using this value, the line of code would be:

$timeInterval = [System.TimeSpan]::Parse("3.03:15:30")

Using the System.TimeSpan.Tick property, you can retrieve the total nanoseconds.  Just multiply by -1 and the value is in the format that needs to be stored to the Active Directory.

We still have one problem to solve, we need to take this 64 bit integer and convert it into a format that ADSI can send to the AD.  We need to convert this value into an IADsLargeInteger interface. I know my solution for this conversion is not the most elegant one, however, I was having trouble trying to figure out how I could remove the top 32 bits of the 64 bit value to load them into the IADsLargeInteger::HighPart.

I used Adam Weigert's Blog post as a basis, then created my own home grown System.TimeSpan to IADsLargeInteger conversion routine.  Many thanks to Adam for posting the conversion information.  Below is a link to his blog post:

<weblogs.asp.net/adweigert/archive/2007/03/23/powershell-convert-active-directory-iadslargeinteger-to-system-int64.aspx>

Once I was able to convert the ticks to IADsLargeInteger types,  the creation of the PSO was a piece of cake. 

Using the TimeSpan object makes it very easy to create human readable time spans.  No mucking about with large conversion multipliers, we let the TimeSpan::Parse method do all the work. 

Below is the PSH code to create a PSO in a 2008 Functional Mode domain:

#
# Conversion function to take TimeSpan objects
# and create IADsLargeInteger objects
#
function ConvertTimeSpanToTimeIntervalIADsLargeInteger([System.TimeSpan] $ts, [object]$olG)
{
    [Int64] $i64 = [Int64]$ts.Ticks * -1;
    $i64
    $bytes = [System.BitConverter]::GetBytes($i64)
    #
    # Ok, I know this is not very elegant or Powershellish....
    # but IT WORKS!
    # I would love to see a better way of working with bits in PSH
    #
    $tmpHigh = [System.Byte[]]@(0,0,0,0);
    $tmpLow = [System.Byte[]]@(0,0,0,0);
    [System.Array]::Copy($bytes,0,$tmpLow,0,4);
    [System.Array]::Copy($bytes,4,$tmpHigh,0,4);
    $lowPart = [System.BitConverter]::ToInt32($tmpLow, 0)
    $highPart = [System.BitConverter]::ToInt32($tmpHigh, 0)
    $oLG.GetType().InvokeMember("HighPart", [System.Reflection.BindingFlags]::SetProperty, $null,  $oLG, @($highPart))
    $oLG.GetType().InvokeMember("LowPart", [System.Reflection.BindingFlags]::SetProperty, $null,  $olG, @($lowPart))
    #
    return $oLG
}
#
# Function to convert IADsLargeIntegers to 64 bit values
# Adam Weigert's conversion routine
#
function ConvertADSLargeInteger([object] $adsLargeInteger)
{
    $highPart = $adsLargeInteger.GetType().InvokeMember("HighPart", [System.Reflection.BindingFlags]::GetProperty, $null, $adsLargeInteger, $null)
    $lowPart  = $adsLargeInteger.GetType().InvokeMember("LowPart",  [System.Reflection.BindingFlags]::GetProperty, $null, $adsLargeInteger, $null)

    $bytes = [System.BitConverter]::GetBytes($highPart)
    $tmp   = [System.Byte[]]@(0,0,0,0,0,0,0,0)
    [System.Array]::Copy($bytes, 0, $tmp, 4, 4)
    $highPart = [System.BitConverter]::ToInt64($tmp, 0)

    $bytes = [System.BitConverter]::GetBytes($lowPart)
    $lowPart = [System.BitConverter]::ToUInt32($bytes, 0)
 
    return $lowPart + $highPart
}
#
# Replace $Server with your target server
# Replace $UserID with your credentials
# Replace $Password with the appropriate password
#
$Server = "my.Server.My.Domain.Com"
$UserID = "Domain\UserID"
$Password = "Password"
#
# Build a ROOT DSE path so you can retrieve the
# DefaultNamingContext
#
$rootpath = "LDAP://" + $Server + "/RootDSE"
$oRoot = new-object System.DirectoryServices.DirectoryEntry($rootpath, $UserID, $Password)
$oRoot.PSBase.RefreshCache()
$strDNC = $oRoot.defaultnamingcontext
#
# Build the path to the default password settings container
#
$strContainerPath = "LDAP://" + $Server + "/CN=Password Settings Container,CN=System," + $strDNC
$strContainerPath
$oPWDContainer = new-object System.DirectoryServices.DirectoryEntry($strContainerPath, $UserID, $Password);
$oPWDContainer.PSBase.RefreshCache()
#
# Create a PasswordSetting Object, then fill it with values
#
$oObj = $oPWDContainer.PSBase.Children.Add("CN=My PSO Object41", "msds-passwordsettings");
#
# Setup the individual ticks objects
# Using KB Defaults described in KB954414:
# support.microsoft.com/kb/954414
#
$oLarge2Days = new-object -ComObject "LargeInteger"
$oLarge1Day = new-object -ComObject "LargeInteger"
$oLarge30Minutes = new-Object -ComObject "LargeInteger"
#
# setup the time spans
#
$TM2days = [System.TimeSpan]::Parse("2.00:00:00");
$TM30Minutes = [System.TimeSpan]::Parse("00:30:00");
$TM1Day = [System.TimeSpan]::Parse("1.00:00:00");
#
# Convert the TimeSpans to Time Interval LargeIntegers
#
ConvertTimeSpanToTimeIntervalIADsLargeInteger $TM2Days $oLarge2Days
ConvertTimeSpanToTimeIntervalIADsLargeInteger $TM1Day $oLarge1Day
ConvertTimeSpanToTimeIntervalIADsLargeInteger $TM30Minutes $oLarge30Minutes
#
# Set the precedence, Reverse Encryption, PasswordLengths and other values
#
$PolicyPrecedence = 20
$ReversibleEncryption = $false
$ComplexityRequirements = $true
$LockoutThreshold = 0
$MinPasswordLength = 8
$HistoryLength = 20
#
# Load up the Properties
#
$oObj.PSBase.Properties["msds-maximumpasswordage"].Value = $oLarge2Days
$oObj.PSBase.Properties["msds-minimumpasswordage"].Value = $oLarge1Day
$oObj.PSBase.Properties["msds-LockoutObservationWindow"].Value = $oLarge30Minutes
$oObj.PSBase.Properties["msds-LockoutDuration"].Value = $oLarge30Minutes
$oObj.PSBase.Properties["msds-LockoutThreshold"].Value =  $LockoutThreshold
$oObj.PSBase.Properties["msds-PasswordSettingsPrecedence"].Value = $PolicyPrecedence
$oObj.PSBase.Properties["msds-PasswordHistoryLength"].Value = $HistoryLength
$oObj.PSBase.Properties["msds-MinimumPasswordLength"].Value = $MinPasswordLength
$oObj.PSBase.Properties["msDS-PasswordComplexityEnabled"].Value = $ComplexityRequirements
$oObj.PSBase.Properties["msDS-PasswordReversibleEncryptionEnabled"].Value = $ReversibleEncryption
$oObj.PSBase.CommitChanges()

Comments