none
Can't pull Max Password Age with vbscrip RRS feed

  • Question

  • Ok, here's the setup.  My company has been building a new 2012 domain environment.  So far, all the servers in the domain are 2012.

    With the new domain, I have begun testing Fine-Grained Password Policies because it was one of the functions we could not do on our old domain.  And so far, everything on the domain side appear to be working just fine.

    So now comes the problem.  Because our users will be using Remote-apps to connect to their servers, they aren't going to be notified that their passwords will be expiring soon.  Thus enters the VBscript.

    Using Microsofts instructions, http://msdn.microsoft.com/en-us/library/ms974598.aspx, I have been trying to get a script going that will pop up a message telling the user that they need to change their password, but I've hit a brick wall with this part of the script.

    Set objDomain = GetObject("LDAP://" & objADSystemInfo.DomainDNSName)
    Set objMaxPwdAge = objDomain.Get("maxPwdAge")
    
        If objMaxPwdAge.LowPart = 0 Then
            WScript.Echo "The Maximum Password Age is set to 0 in the " & _
                         "domain. Therefore, the password does not expire."
            WScript.Quit

    For some reason, it will not pull the max password age.  Now I'm using Fine-Grained passwords for the test account, but I have also tried setting the max password age in the default domain policy, and I still get the message that the "age is set to 0 and the password will not expire" even though I know the policy is functioning on the account.

    I was thinking I might need to try and get the information from this attribute http://msdn.microsoft.com/en-us/library/cc220303.aspx, but I am unsure of how to call this information.

    If I can just get the script to pull the max password age, I believe I can get the rest of the script working.


    • Edited by Hotpocketdeath Wednesday, July 3, 2013 2:16 PM code correction
    Wednesday, July 3, 2013 1:27 PM

Answers

All replies

  • You may want to post this question in the Scripting forum:

    http://social.technet.microsoft.com/Forums/scriptcenter/en-US/home?forum=ITCG


    Santhosh Sivarajan | Houston, TX

    Windows 2012 Book - Migrating from 2008 to Windows Server 2012

    http://www.sivarajan.com/
    FaceBookTwitter LinkedIn SS Tech Forum
    This post is provided ASIS with no warran

    • Proposed as answer by Meinolf Weber Wednesday, July 3, 2013 5:57 PM
    Wednesday, July 3, 2013 2:09 PM
    Moderator
  • Hi,

    Take a look at this URL: http://www.activexperts.com/admin/scripts/vbscript/0176/

    Best regards,

    /Jimmy


    Best regards, /Jimmy Andersson

    Wednesday, July 3, 2013 3:31 PM
  • You may want to post this question in the Scripting forum:

    http://social.technet.microsoft.com/Forums/scriptcenter/en-US/home?forum=ITCG


    Santhosh Sivarajan | Houston, TX

    Yeah, I will probably get better advise in that section.  I went ahead and started a thread in that section as well.
    Wednesday, July 3, 2013 4:47 PM
  • Hi,

    Take a look at this URL: http://www.activexperts.com/admin/scripts/vbscript/0176/

    Best regards,

    /Jimmy


    Best regards, /Jimmy Andersson

    Much appreciated, but still doesn't work.

    The odd thing is that only the maximum password age isn't working.  Using the script in the link, I get a result of -1.15740740740741E-05 and I have it currently set to 365 days in the Default Domain Policy.  I got rid of the fine grained policy until I can get this working.

    But yet, for items like minimum password age, password history, lockout durations, etc.  I'm getting exactly what I would expect and matches what I have set in policy.


    • Edited by Hotpocketdeath Wednesday, July 3, 2013 4:52 PM More detail
    Wednesday, July 3, 2013 4:51 PM
  • If you open up adsiedit, connect to the Default Naming Context and open up the properties on the domain what do you see for maxPwdAge? I don't recall where I got the code from in line 3 but I've always used the below to read this out:

    Set oDomain = GetObject("LDAP://" & GetObject("LDAP://rootDSE").Get("defaultNamingContext"))
    Set maxPwdAge = oDomain.Get("maxPwdAge")
    numDays = CCur((maxPwdAge.HighPart * 2 ^ 32) + maxPwdAge.LowPart) / CCur(-864000000000)
    Wscript.echo numDays

    I hope this helps,
    Mark.

     

    Wednesday, July 3, 2013 5:31 PM
  • Ok, I went back to the drawing board and got the script to work.  Problem is that it still wants to pull the maxPwdAge from the Domain Policy instead of the Fine-Grained Policy.

    Full Script

    On Error Resume Next
    Const ADS_UF_DONT_EXPIRE_PASSWD = &h10000
    Const E_ADS_PROPERTY_NOT_FOUND  = &h8000500D
    Const ONE_HUNDRED_NANOSECOND    = .000000100
    Const SECONDS_IN_DAY            = 86400
    Const NO_EXPIRE                 = 99999
    
    '**************************************************************
    '* Function to get number of days left before password change *
    '**************************************************************
    
    Function PasswordDaysLeft()
    
    'Default to zero days left...an error can occur if the user
    'is forced to change their password. This will cover that
    PasswordDaysLeft = 0
    
    Set objADSystemInfo = CreateObject("ADSystemInfo")              
    Set objUser = GetObject("LDAP://" & objADSystemInfo.UserName)   
    
    
    intUserAccountControl = objUser.Get("userAccountControl")
    If intUserAccountControl And ADS_UF_DONT_EXPIRE_PASSWD Then
        PasswordDaysLeft = NO_EXPIRE
        Exit Function
    Else
        dtmValue = objUser.PasswordLastChanged
        If Err.Number = E_ADS_PROPERTY_NOT_FOUND Then
            PasswordDaysLeft = NO_EXPIRE
    	Exit Function
        Else
            intTimeInterval = Int(Now - dtmValue)
        End If
    
        Set objDomain = GetObject("LDAP://" & objADSystemInfo.DomainDNSName)
        Set objMaxPwdAge = objDomain.Get("maxPwdAge")
    
        If objMaxPwdAge.LowPart = 0 Then
    	PasswordDaysLeft = NO_EXPIRE
            Exit Function
        Else
            dblMaxPwdNano = _
                Abs(objMaxPwdAge.HighPart * 2^32 + objMaxPwdAge.LowPart)
            dblMaxPwdSecs = dblMaxPwdNano * ONE_HUNDRED_NANOSECOND
            dblMaxPwdDays = Int(dblMaxPwdSecs / SECONDS_IN_DAY)
    
            If intTimeInterval >= dblMaxPwdDays Then
             PasswordDaysLeft = 0
            Else
             PasswordDaysLeft = Int((dtmValue + dblMaxPwdDays) - Now)
            End If
        End If
    End If
    
    End Function
    
    '**************************************************************
    '* Main Application Loop                                      *
    '**************************************************************
    
    'Sleep for 1 minutes
    WScript.Sleep(1 * 1000 * 60)
    
    bRunning = TRUE
    '1 minutes is up, check every 1 min to see if password is too old
    while (bRunning)
     if (PasswordDaysLeft() <= 14) then
      MsgBox("Your password is about to expire.")
      bRunning = FALSE
     end if
     WScript.Sleep(1 * 1000 * 60)
    wend 

    Domain max password age is 365

    Fine-Grained password age is 150

    Test account password was set 146 days ago.  For testing, I changed the line "if (PasswordDaysLeft() <= 14) then" to "if (PasswordDaysLeft() <= 219) then", the script works and I get a popup saying it's about to expire.  But that's a dead giveaway that it's using the domain max age.



    Wednesday, July 3, 2013 6:39 PM
  • When you bind to the default naming context and read maxPwdAge you are reading the default domain account policy that's written there by the PDCe DC. This series of articles does a good job of explaining this relationship: http://jorgequestforknowledge.wordpress.com/2010/09/27/password-policies-and-account-lockout-policies-within-an-ad-domain-part-1/

    I haven't used fine-grained password policies in the past so I unfortunately can't test this but by the looks of the following article you need to bind to the user account in question and attempt to retrieve the msDS-ResultantPSO constructed attribute: http://technet.microsoft.com/en-us/library/cc770848(v=ws.10).aspx If that's null then you need to do the maths with PasswordLastChanged against maxPwdAge. If it's not null then you need your script to do a bit more processing to find the relevant PSO in the Password Settings Container and read the equivalent of maxPwdAge out of that PSO. By the looks of this article, that is the msDS-MaximumPasswordAge  attribute:
    http://technet.microsoft.com/en-us/library/cc754461(v=ws.10).aspx
    I hope this helps,
    Mark

     
    Wednesday, July 3, 2013 8:13 PM
  • As Mark mentioned, your script is only looking at the Default Domain Policy. I too am trying to do what you are doing, however am getting stuck on the coding of it. What i have so far is:

    On Error Resume Next
    Dim objSysInfo, objUser, ResultantPSO
    Set objSysInfo = CreateObject("ADSystemInfo")
    Set objUser = GetObject("LDAP://" & objSysInfo.UserName)
    objUser.GetInfoEx Array("msDS-ResultantPSO"),0
    ResultantPSO = objUser.get("msDS-ResultantPSO")
    WScript.Echo "PSO: " & ResultantPSO
    Set objSysInfo = nothing
    Set ibjUser = nothing
    Set ResultantPSO = nothing

    The above script will retrieve the currently active PSO that is set for the user (and is generated on the fly by using GetInfoEx).

    From there i tried to add the following code *BUT IT DOESNT WORK* and I cant seem to work out what im doing wrong.

    Set objPSO = GetObject("LDAP://" & ResultantPSO)
    Set MaxPWAge = objPSO.get("msDS-MaximumPasswordAge")
    WScript.Echo "MaxPWAge: " & maxPWAge


    In theory, a combination of your code and mine would get the script to work, with the following workflow:

    1. Get current logged on user details
    2. Retrieve ResultantPSO setting.
    3. If exist, retrieve MaximumPasswordAge from that particular PSO object, otherwise retrieve domain policy object.
    4. Set either value to the same variable, of objMaxPwdAge
    5. calculate the time to days
    6. Prompt user if date is within the threshold.

    If anyone is able to combine the two scripts, i would greatly appreciate it as I am not able to figure out how to do it. (i was trying to not search search, and use the same method as for retrieving user info but i dont think its possible).

    Kind Regards,
    Ivan
    http://ivan.dretvic.com


    Tuesday, December 31, 2013 12:52 AM
  • From there i tried to add the following code *BUT IT DOESNT WORK* and I cant seem to work out what im doing wrong.

    Set objPSO = GetObject("LDAP://" & ResultantPSO)
    Set MaxPWAge = objPSO.get("msDS-MaximumPasswordAge")
    WScript.Echo "MaxPWAge: " & maxPWAge

    I was working on this as well and finally figured out a solution.  

    Set objPSO = GetObject("LDAP://" & ResultantPSO)
    Set objMaxPwdAge = objPSO.Get("msDS-MaximumPasswordAge")
    
    intPwdAge = (((objPwdAge.HighPart * (2^32) + objPwdAge.LowPart) / (60 * 10000000)) / 1440) * -1
    
    Wscript.Echo "The " & objPSO.CN & " maximum password age is " & intPwdAge & "."

    From what I have learned, 64-bit integers are not supported in VBScript, which is why we have to do some Integer magic.  

    Hotpocketdeath's last post used the same formula, so the answer was not that far away.  However, I ended up using the following link as a resource.  I combined to one line and multiplied by -1 to make it a positive integer.    

    http://blogs.technet.com/b/heyscriptingguy/archive/2010/01/27/dandelions-vcr-clocks-and-last-logon-times-these-are-a-few-of-our-least-favorite-things.aspx


    Jasen Webster Disclaimer: This posting is provided "AS IS" with no warranties or guarantees , and confers no rights.


    • Edited by jaylweb Tuesday, September 2, 2014 7:59 PM Formatting
    Tuesday, September 2, 2014 7:57 PM
  • Hi there Jasen

    What did your final script look like please.

    Thanks

    Friday, August 7, 2015 3:56 AM
  • Hi there Jasen

    What did your final script look like please.

    Thanks

    Sorry for the late response.  I ended up not using this.  Originally,we were going to set a 120 day password policy, then run a VBScript at user logoff.  If the user was within 30 days, the script would tick the box to change password at next logon.  This was to have passwords change at 90 days, but not expire while they were logged in. Here is that VBScript.  

    ' 05-22-2014 JLW
    ' Created to run in GPO logoff script
    ' This will trigger the Change Password at Next Logon
    
    ' Set Variables
    intTimeZoneOffset = -5
    
    ' Set constants
    CONST ADS_UF_DONT_EXPIRE_PASSWD = 65536
    
    ' Obtain local Time Zone bias from machine registry.
    ' This bias changes with Daylight Savings Time.
    Set objShell = CreateObject("Wscript.Shell")
    lngBiasKey = objShell.RegRead("HKLM\System\CurrentControlSet\Control\" _
        & "TimeZoneInformation\ActiveTimeBias")
    If (UCase(TypeName(lngBiasKey)) = "LONG") Then
        lngTZBias = lngBiasKey
    ElseIf (UCase(TypeName(lngBiasKey)) = "VARIANT()") Then
        lngTZBias = 0
        For k = 0 To UBound(lngBiasKey)
            lngTZBias = lngTZBias + (lngBiasKey(k) * 256^k)
        Next
    End If
    
    ' Connect to AD and get current user
    Set objSysInfo = CreateObject("ADSystemInfo")
    Set objUser = GetObject("LDAP://" & objSysInfo.UserName)
    
    ' Check if user has No Password Expire flag set. Script exits if they do.  
    intUAC = objUser.GET("userAccountControl")
    If (intUAC AND ADS_UF_DONT_EXPIRE_PASSWD) THEN
    	WScript.Quit
    End If
    
    ' Get current users resultant password setting object
    objUser.GetInfoEx Array("msDS-ResultantPSO"),0
    On Error Resume Next
    ResultantPSO = objUser.get("msDS-ResultantPSO")
    
    If err.Number <> 0 Then
    	WScript.Quit
    End If
    On Error goto 0
    
    ' Get the resultant password setting object and determining maximum password age
    Set objPSO = GetObject("LDAP://" & ResultantPSO)
    Set objMaxPwdAge = objPSO.Get("msDS-MaximumPasswordAge")
    intMaxPwdAge = ((Integer8Date(objMaxPwdAge, False)) - #1/1/1601#) * -1
    
    ' objUser.GetInfoEx Array("msDS-UserPasswordExpiryTimeComputed"),0
    ' Set objPwdExpiry = objUser.Get("msDS-UserPasswordExpiryTimeComputed")
    ' wscript.echo CDate(Integer8Date(objPwdExpiry, lngBiasKey))
    
    ' Get the current users last password set date
    If (TypeName(objUser.pwdLastSet) = "Object") Then
        Set objDate = objUser.pwdLastSet
        dtmPwdLastSet = Integer8Date(objDate, lngBiasKey)	
    Else
        dtmPwdLastSet = #1/1/1601#
    End If
    
    ' Password expiration date is determined by the last password set date plus the maximum password age. 
    dtmPwdExpireDate = dtmPwdLastSet + intMaxPwdAge
    
    ' Days until password expired is the difference between the current date and the password expiration date.  This returns only the number of days.  
    dtmPwdExpireDays = DateDiff("d", Date, dtmPwdExpireDate)
    
    ' WScript.Echo "PSO Name:  " & objPSO.CN & VBCRLF & VBCRLF & "Maximum password age is:  " _
    	' & intMaxPwdAge & VBCRLF & VBCRLF & "Password last set on:  " & dtmPwdLastSet & VBCRLF & VBCRLF _
    	' & "Password expires at:  " & dtmPwdExpireDate & VBCRLF & VBCRLF & "Password expires in " & dtmPwdExpireDays & " days."
    ' WScript.Quit
    
    'Require password change the next time the user logs on, if password expires within 30 days.
    If (dtmPwdExpireDays <= 30 AND intMaxPwdAge <> 0) Then
    	objUser.pwdLastSet = 0
    	objUser.SetInfo
    End If
    
    Function Integer8Date(ByVal objDate, ByVal lngBias)
        ' Function to convert Integer8 (64-bit) value to a date, adjusted for
        ' local time zone bias.
        Dim lngAdjust, lngDate, lngHigh, lngLow
        lngAdjust = lngBias
        lngHigh = objDate.HighPart
        lngLow = objdate.LowPart
        ' Account for error in IADsLargeInteger property methods.
        If (lngLow < 0) Then
            lngHigh = lngHigh + 1
        End If
        If (lngHigh = 0) And (lngLow = 0) Then
            lngAdjust = 0
        End If
        lngDate = #1/1/1601# + (((lngHigh * (2 ^ 32)) _
            + lngLow) / 600000000 - lngAdjust) / 1440
        ' Trap error if lngDate is ridiculously huge.
        On Error Resume Next
        Integer8Date = CDate(lngDate)
        If (Err.Number <> 0) Then
            On Error GoTo 0
            Integer8Date = #1/1/1601#
        End If
        On Error GoTo 0
    End Function

    In the end, I used a Power Shell script via task scheduler on a domain controller.  The Power Shell script would check if the user password changed within the past 23 hours, then tick/untick the Change at next logon, which would set the password change date/time to when the script ran.  This allows passwords to expire when the task is run (at 3am for us), instead of while they are logged in.  

    Here is my powershell script 

    ########################################################################
    # ScriptName: Change_User_PwdLastSet_v1.ps1
    # Version 1.0
    # Author: Jasen Webster
    # Created: 09-29-2014
    # 
    # Problem Case:
    # =============
    #
    # When a user is logged in his desktop and he is away from his desktop and the screen is locked.
    # In the meantime the password expires and user wants to login again, Windows 7 is telling you to change your password.
    # To change the password a user have to click 'Switch User'. The process is very confusing and time consuming for the user.
    #
    # 
    # Problem Solution:
    # =================
    # 
    # To solve the situation, this script changes the 'Password last set' attribute for all users who changed their password during the 
    # last 23 hours. Most of the users login during daylight hours, so running this at 3 AM should have little impact.  When users login 
    # in the morning, they get a Password Expiration warning and are forced to change their password right away and not during the day
    # at an inappropriate time.
    #
    # The script changes the time stamp for the AD User Property 'PwdLastSet'.
    # This property will be set to the current date and time when the script is run.
    # PwdLastSet + PasswordPolicy = Password Expiration
    #
    # 
    # Example user:
    # =============
    # 
    # Password policy for this user is: change password policy every 100 days
    #
    # - 08:05h : UserA logs in to his desktop and get the message to change his password.
    #            The user is changing his password and works all day without problems.
    # - 03:00h : This script is running and is checking which users changed their password for the last 23 hours.
    #            The script will change for all these users their 'PwdLastSet' attribute to the current date and time
    # 
    # - 90 days later at 03:00h the password for UserA will expire
    # - The next morning when UserA comes in the building he gets prompt to change his password again.
    #
    #  Modified:
    # =============
    #
    # 09-29-2014 JLW: Added Import-Module ActiveDirectory because the AD module needs to be loaded.  Also setup variables. 
    # 09-29-2014 JLW: Removed syntax error.  Changed $lastchange = [datetime]::FromFileTime($_.pwdlastset[0]) to 
    #		$lastchange = [datetime]::FromFileTime($_.pwdlastset).  The pwdlastset attribute is not an array, so [0] caused an error.
    # 10-01-2014 JLW: Changed the way the logfile name is created.  Moved $logdate variable to the variables section to precede the $logfile variable
    # 10-02-2014 JLW: Changed the $logfile name to be generated based on the scripts name and the $logfile location to be in the same folder as the script.
    #
    #
    #
    #
    ########################################################################
    
    ########################################################################
    # Load the ActiveDirectory module
    ########################################################################
    Import-Module ActiveDirectory
    
    
    ########################################################################
    # Setting variables 
    ########################################################################
    $ScriptPath = $MyInvocation.MyCommand.Path
    $ScriptDir = split-path -parent $ScriptPath
    $ScriptName = [system.io.path]::GetFilenameWithoutExtension($ScriptPath)
    $logdate = Get-Date -format yyyyMMdd
    $logfile = ($ScriptDir + "\" + $ScriptName + "_$logdate.log")	
    $DN_OU_Path = "DC=test,DC=local,DC=com"	 
    $hourschange_sincePwdChange = 23
    
    
    ########################################################################
    # Create LogFile
    ########################################################################
    Add-Content $logfile "samaccountname, lastchange, today, hoursdiff"
    
    
    ########################################################################
    # Grab users in AD
    ########################################################################
    Get-ADUser -Filter * -SearchScope Subtree -SearchBase "$DN_OU_Path" -Properties Name,pwdLastSet | select SamAccountName,pwdLastSet |
    
    
    ########################################################################
    # Run for all users found
    ########################################################################
    ForEach-Object {
      $samaccountname = $_.SamAccountName
      $today = Get-Date
      
      # Convert the Date in a good Format
      $lastchange = [datetime]::FromFileTime($_.pwdlastset) 
    
      # Get the timedifference between last password change and the current date/time
      $timediff = New-TimeSpan $lastchange $(Get-Date)
      
      # Convert to difference in hours
      $hoursdiff = $timediff.TotalHours
    
      if ($hoursdiff -lt $hourschange_sincePwdChange) {
        $todouser = Get-ADUser $samaccountname -Properties pwdLastSet
        
        # First change the pwdlastset to 0 to reset the password set date/time
        $todouser.pwdLastSet = 0
        Set-ADUser -Instance $todouser
        
        # Change the pwdlastset to -1, which will set it to the current date/time
        $todouser.pwdLastSet = -1
        Set-ADUser -Instance $todouser
    
        # Add data to logfile
        Add-Content $logfile "$samaccountname, $lastchange, $today, $hoursdiff"
      }
    
    }
    The only issue we have is from mobile devices syncing with Exchange.  We lowered our lockout thresholds/observation/duration to 5/30/120.  For the most part, if Active Sync locks out the account, it will be unlocked by the time they come to work.  

    Jasen Webster Disclaimer: This posting is provided "AS IS" with no warranties or guarantees , and confers no rights.


    • Edited by jaylweb Tuesday, November 3, 2015 9:36 PM Cleaned up sensitive info
    Tuesday, November 3, 2015 9:31 PM