0 votes

Right now I have a scheduled task that runs on all user accounts and sends and HTML email to users when their password will expire. The problem with this method is that it runs on every account, every day, which causes a lot of spam in the Adaxes logs for "No Operations Executed". This clutters the Management History log view when trying to determine what changes have been made to an account.

What I am considering is to convert the password notification to a Custom Command in Adaxes. Then I want to create a new scheduled task that runs a powershell script to search a list of OUs in AD for user accounts with expiring passwords. The script would then execute the Custom Command only on accounts found in the search. This would lead to log entries on the user objects only being created when the command actually sends an email.

I have a template script that does search a list of OUs based on an the Adaxes searcher and an LDAP filter, but I'm not sure how to adapt that to look for users with expiring passwords. I could use some help sorting that out.

by (290 points)
0

I think I've figured it out by combining my searcher template with the script from the Soon-To-Expire Passwords report script. I'm getting an error with line 89 and the msDS-ResultantPSO property however. Here is my current script.

# Get parameter values
$days = 14 # TO DO: Modify Me - How soon password expires
$now = Get-Date
$nowInt64 = $now.ToFileTime()
$threshold = $now.AddDays($days)
$thresholdInt64 = $threshold.ToFileTime()
$commandID = "{d8ff58b9-8131-4041-9861-2b73a02983e1}" # TO DO: Modify Me

# List of OUs to search
$ouDNs = @("OU=User OU 1,DC=domain,DC=com")
$ouDNs += "OU=User OU 2,DC=domain,DC=com"
$ouDNs += "OU=User OU 3,DC=domain,DC=com"

[Reflection.Assembly]::LoadWithPartialName("Softerra.Adaxes.Adsi")

# Connect to the Adaxes service
$admNS = New-Object "Softerra.Adaxes.Adsi.AdmNamespace"
$admService = $admNS.GetServiceDirectly("localhost")

Function FindUsers ($ouPath, $officesInfo) {
    $searcher = New-Object "Softerra.Adaxes.Adsi.Search.DirectorySearcher" $NULL, $False
    $searcher.SearchParameters.PageSize = 500
    $searcher.SearchParameters.SearchScope = "ADS_SCOPE_SUBTREE"
    $searcher.SearchParameters.BaseObjectPath = "$ouPath"
    ### Build search filter ###
    # We filter out user accounts whose password never expires or must be changed at next logon,
    # as well as interdomain trust accounts and accounts required to use smart cards
    $filterUsers = "(sAMAccountType=805306368)"
    $filterPasswordLastSet = "(!(pwdLastSet=0))"
    $filterPasswordNeverExpires = "(!(userAccountControl:1.2.840.113556.1.4.803:=65536))"
    $filterSmartCardLogon = "(!(userAccountControl:1.2.840.113556.1.4.803:=262144))"
    $filterInterdomainTrust = "(!(userAccountControl:1.2.840.113556.1.4.803:=2048))"
    $filterDisableUsers = "(!(userAccountControl:1.2.840.113556.1.4.803:=2))"
    $filter = "(&" + $filterUsers + $filterPasswordLastSet + $filterPasswordNeverExpires + $filterSmartCardLogon + $filterInterdomainTrust + $filterDisableUsers +")"
    $searcher.SearchParameters.Filter = $filter
    $searcher.SearchParameters.ReferralChasing = "ADS_CHASE_REFERRALS_NEVER"
    # Load these properties
    $searcher.SetPropertiesToLoad(@("msDS-UserPasswordExpiryTimeComputed","msDS-ResultantPSO","pwdLastSet","distinguishedName","userPrincipalName"))
    $result = $searcher.ExecuteSearch()
    $users = $result.FetchAll()
    $result.Dispose()

    # Map domain names to maximum password age specified in the default domain password policy
    $domainToMaxPwdAge = @{}

    # Map policy DN to its name
    $policyDnToName = @{}

    # Custom column identifiers
    $effectivePolicyColumnID = "{a9cc0207-e218-4392-aff2-01222f74b001}"
    $passwordExpiresColumnID = "{41055ea7-4c35-4025-ba3a-5d65871b4a59}"

    # Find users with soon-to-expire passwords
    ForEach ($user in $users) {
        $username = $user.Properties["userPrincipalName"].Value
        $dn = $user.Properties["distinguishedName"].Value
        $expiryTime = $user.GetPropertyByName("msDS-UserPasswordExpiryTimeComputed").Values[0]

        If ($expiryTime -eq $NULL) # Fine-Grained Password Policies not supported
        {
            # Get the maximum password age from the default domain password policy
            $domain = $Context.GetObjectDomain($user.AdsPath.DN)
            If (-not $domainToMaxPwdAge.Contains($domain))
            {
                # Bind to the default naming context
                $nc = $Context.BindToObjectEx("Adaxes://$domain", $False)
                # Get the value of the maxPwdAge property
                $maxPwdAgeLargeInt = $nc.Get("maxPwdAge")
                # Remember the value
                $domainToMaxPwdAge[$domain] =
                    [Softerra.Adaxes.Adsi.AdsLargeInteger]::ToInt64($maxPwdAgeLargeInt)
            }

            $maxPwdAge = $domainToMaxPwdAge[$domain]
            If ($maxPwdAge -eq 0x8000000000000000) # no maximum password age
            {
                $expiryTime = $maxPwdAge
            }
            Else
            {
                $passwordLastSet = $user.GetPropertyByName("pwdLastSet").Values[0]
                $expiryTime = $passwordLastSet - $maxPwdAge
            }
        }

        If (($expiryTime -gt $nowInt64) -and ($expiryTime -lt $thresholdInt64))
        {
            # Get effective policy name
            #$policyDN = $user.GetPropertyByName("msDS-ResultantPSO").Values[0]
            $policyDN = $NULL
            If ([String]::IsNullOrEmpty($policyDN))
            {
                $policyName = "<Default Domain Policy>"
            }
            Else
            {
                If (-not $policyDnToName.Contains($policyDN))
                {
                    $policyDNObj = New-Object Softerra.Adaxes.LDAP.DN $policyDN
                    $policyDnToName[$policyDN] = $policyDNObj.Leaf.Value
                }
                $policyName = $policyDnToName[$policyDN]
            }
            $columnValues = @{ $effectivePolicyColumnID=$policyName; $passwordExpiresColumnID=$expiryTime }
            #$Context.Items.Add($user, $columnValues, $NULL)

            # Bind to the user
            $userObj = $admService.OpenObject("Adaxes://$dn", $NULL, $NULL, 0)

            # Execute Custom Command
            $userObj.ExecuteCustomCommand($commandID)
        }
    }
}

ForEach ($ouDN in $ouDNs)
{
    # Bind to OU
    $ou = $Context.BindToObjectByDN($ouDN)

    # Build report part for current OU
    $ouName = [Softerra.Adaxes.Utils.ObjectNameHelper]::GetObjectName($ou, 'IncludeParentPath')

    # Add users
    $ouMatches = $NULL
    $ouMatches = FindUsers $ou.ADsPath $ouName
}

1 Answer

0 votes
by (216k points)

Hello,

The error occurs because msDS-ResultantPSO is an operational attribute whose value is calculated by a domain controller on request. You should use the Get-ADUserResultantPasswordPolicy cmdlet to obtain the attribute value.

For your information, you can configure Adaxes logging to not log the Execute scheduled task operation. It should prevent creation of the "No Operations Executed" log records. For details, see https://www.adaxes.com/help/?Logging.EditServiceLogSettings.html. The settings should be like the following: image.png

Related questions

0 votes
1 answer

Is it possible to setup a scheduled task for password expiration notifier to send one email a day for accounts whose password will expire in less than X amount of days that have ... expiring on the same day. There will be a lot of emails going at once.

asked Mar 20 by tromanko (330 points)
0 votes
1 answer

I'm using the default builtin password expiration notifier. I have it set to run everyday at 8AM, to check if the password will expire in &lt;7 days, and send an ... saw the task had been running for nearly 24 hours, no completion. What is happening here?

asked Sep 12, 2023 by keecit (60 points)
0 votes
1 answer

I want to send reminder emails that a users password is going to expire when their password is going to expire in 14, 7, 3, and 1 days. I have setup 4 ... a daily PowerShell script, where I do the actual password expiration check in the PowerShell script?

asked Aug 9, 2017 by HDClown (220 points)
0 votes
1 answer

Can you help us understand how the password expiration is sourced, is it calculated by Adaxes or just displayed based on an AD attribute? We have some users that apparently do ... the field "Password Expiration Date" but we don't know how that is populated.

asked Jul 28, 2015 by theckel (520 points)
0 votes
1 answer

Hi, we are using a scheduled job in Adaxes to notify users that their password will expire in x days. Now, we as IT were approached by Marketing to set up all ... ;/div&gt; &lt;/body&gt; &lt;/html&gt; Your help would be highly appreciated kind regards Ingemar

asked Mar 2, 2015 by ijacob (960 points)
3,572 questions
3,261 answers
8,277 comments
547,994 users