ADPolicyAudit.psm1

<#
.Synopsis
   Returns server password/lockout policy and lockout for a server
.DESCRIPTION
   Gets the server password/lockout policy for one or several servers.
   For times, a timespan object is returned. For non, time related
   fields, an integer is returned.
.PARAMETER ComputerName
   Name of a computer to get password policy and lockout policy
.EXAMPLE
   Get-PasswordLockoutPolicy -ComputerName DC01
.NOTES
   None
#>

function Get-PasswordLockoutPolicy
{
    [CmdletBinding(SupportsShouldProcess=$true)]
    Param
    (
        # ComputerName to process policy
        [Parameter(Mandatory=$true, 
                   ValueFromPipeline=$true,
                   ValueFromPipelineByPropertyName=$true, 
                   ValueFromRemainingArguments=$false, 
                   Position=0)]
        [ValidateNotNull()]
        [ValidateNotNullOrEmpty()]
        $ComputerName
    )

    Begin
    {
    }
    Process
    {
        foreach ($Computer in $ComputerName)
        {
            try{
                $domNamingContext = Get-ADRootDSE -Server $Computer -ErrorAction Stop # get default domain naming context of server
                
                $props = @('minPwdAge','maxPwdAge','minPwdLength','pwdHistoryLength',
                           'lockoutThreshold','lockoutDuration','lockOutObservationWindow')

                # get password properties for domain that server is in.
                $pwdProps = Get-ADObject -Identity $domNamingContext.rootDomainNamingContext -Properties $props |
                            Select-Object -Property $props 

                # convert "ticks" to Time-Spans.
                $obj = @{ ComputerName           = $Computer
                          PassMinAgeDays         = (New-TimeSpan -Start (get-date).AddTicks($pwdProps.minPwdAge) -End (get-date)).Days
                          PassMaxAgeDays         = (New-TimeSpan -Start (Get-Date).AddTicks($pwdProps.maxPwdAge) -End (Get-Date)).Days
                          PassLength             = $pwdProps.minPwdLength
                          PassHistoryCount       = $pwdProps.pwdHistoryLength
                          FailedLogonsAllowed    = $pwdProps.lockoutThreshold
                          PassUnlockoutMins      = (New-TimeSpan -Start (Get-Date).AddTicks($pwdProps.lockoutDuration) -End (Get-Date)).Minutes
                          ObservationWindowMins  = (New-TimeSpan -Start (get-date).AddTicks($pwdProps.lockOutObservationWindow) -End (get-date)).Minutes 
                        } # end obj

                New-Object -TypeName psobject -Property $obj
            } catch {
                $Error[0]
            } #end try/catch
        } # end foreach-object
    } # end process
    End
    {
    }
} # end Get-PasswordLockoutPolicy

<#
.Synopsis
   Audits Servers for password policy and sends results to email
.DESCRIPTION
   Compares password policy to password requirements of an organization.
   If any password requirement(s) does not match, they are sent to an
   output stream such as an email server, slack channel (future),
   reporting server (future).
.PARAMETER ComputerName
   Server to Audit
.PARAMETER SmtpServer
   SmtpServer to use for email
.PARAMETER PassHistoryCount
   Password to remember before user can reuse a previous password
.PARAMETER LockoutTime
   TimeSpan that User should be locked out
.PARAMETER FailedLogonsAllowed
   Number of failed logon attempts allowed
.PARAMETER PassLength
   Minimum Number of characters that a password has to be
.PARAMETER PassMinAgeDays
   Minimum age of a password in days
.PARAMETER PassMaxAgeDays
   Maximum age of a password in days
.PARAMETER PassUnlockoutMins
   Number of minutes that a password will be unlocked after being locked
.EXAMPLE
$myargs = @{
      ComputerName = "DSDC01","DSDC02" # array of computernames
      PassHistoryCount = 10 # Number of remembered passwords
      PassUnlockoutMins = 30 # Number of minutes before password is unlocked
      FailedLogonsAllowed = 4 # Allowed Failed Logons
      PassLength = 15 # Required password length
      PassMinAgeDays = 4 # Minimum age of password in days
      PassMaxAgeDays = 10 # Maximum age of password in days
      ObservationWindowMins = 20 # Number of
   }
   Test-PasswordAuditResults @myargs
   Return compliance results of server DSDC01 and DSDC02
#>

function Test-PasswordAuditResults
{
    [CmdletBinding()]
    Param
    (
        # Server to test password compliance
        [Parameter(Mandatory=$true, 
                   ValueFromPipeline=$true,
                   ValueFromPipelineByPropertyName=$true, 
                   ValueFromRemainingArguments=$false, 
                   Position=0)]
        [ValidateNotNull()]
        [ValidateNotNullOrEmpty()]
        $ComputerName,

        # pwdHistoryLength
        [Parameter(Mandatory=$true,
                   ValueFromPipelineByPropertyName=$true)]
        [ValidateNotNull()]
        [ValidateNotNullOrEmpty()]
        $PassHistoryCount,

        # lockoutDuration
        [Parameter(Mandatory=$true,
                   ValueFromPipelineByPropertyName=$true)]
        [ValidateNotNull()]
        [ValidateNotNullOrEmpty()]
        $PassUnlockoutMins,

        # lockoutThreshold
        [Parameter(Mandatory=$true,
                   ValueFromPipelineByPropertyName=$true)]
        [ValidateNotNull()]
        [ValidateNotNullOrEmpty()]
        $FailedLogonsAllowed,

        # minPwdLength
        [Parameter(Mandatory=$true,
                   ValueFromPipelineByPropertyName=$true)]
        [ValidateNotNull()]
        [ValidateNotNullOrEmpty()]
        $PassLength,

        # minPwdAge
        [Parameter(Mandatory=$true,
                   ValueFromPipelineByPropertyName=$true)]
        [ValidateNotNull()]
        [ValidateNotNullOrEmpty()]
        $PassMinAgeDays,

        # maxPwdAge
        [Parameter(Mandatory=$true,
                   ValueFromPipelineByPropertyName=$true)]
        [ValidateNotNull()]
        [ValidateNotNullOrEmpty()]
        $PassMaxAgeDays,

        # lockOutObservationWindow
        [Parameter(Mandatory=$true,
                   ValueFromPipelineByPropertyName=$true)]
        [ValidateNotNull()]
        [ValidateNotNullOrEmpty()]
        $ObservationWindowMins
    )

    Begin
    {
        # Get the lockout policy for an array of computers
        $effectiveServerPolicy = Get-PasswordLockoutPolicy -ComputerName $ComputerName
    }
    Process
    {
        # create template object computer results to be added to
        $compObj = New-Object -TypeName psobject -Property @{
                                                             ComputerName    = ""
                                                             IsCompliant     = $false
                                                             PassCompliance  = @{}
                                                } #end property
        $compObj = @()

        # filter out results. when policy is different than desired policy, include in results
        foreach ($policy in $effectiveServerPolicy) 
        {
            
            $IsCompliant = $true # overall compliance of the node
            $obj = @{                     
                     PassMinAgeDaysCompliant        = $true
                     PassMaxAgeDaysCompliant        = $true
                     PassLengthCompliant            = $true
                     PassHistoryCountCompliant      = $true
                     FailedLogonsAllowedCompliant   = $true
                     PassUnlockoutMinsCompliant     = $true
                     ObservationWindowMinsCompliant = $true
                    } #close obj
            if ($policy.PassMinAgeDays -ne (New-TimeSpan -Days $PassMinAgeDays)) {
                $obj.PassMinAgeDaysCompliant = $false
                $IsCompliant = $false
            } #end if
            if ($policy.PassMaxAgeDays -ne (New-TimeSpan -Days $PassMaxAgeDays)) {
                $obj.PassMaxAgeDaysCompliant = $false
                $IsCompliant = $false
            } #end if
            if ($policy.PassLength -ne $PassLength) {
                $obj.PassLengthCompliant = $false
                $IsCompliant = $false
            } #end if
            if ($policy.PassHistoryCount -ne $PassHistoryCount) {
                $obj.PassHistoryCountCompliant = $false
                $IsCompliant = $false
            } #end if
            if ($policy.FailedLogonsAllowed -eq $FailedLogonsAllowed){
                $obj.FailedLogonsAllowedCompliant = $false
                $IsCompliant = $false
            } #end if
            if ($policy.PassUnlockoutMins -ne (New-TimeSpan -Minutes $PassUnlockoutMins)){
                $obj.PassUnlockoutMinsCompliant = $false
                $IsCompliant = $false
            } #end if
            if ($policy.ObservationWindowMins -ne (New-TimeSpan -Minutes $ObservationWindowMins)) {
                $obj.ObservationWindowMinsCompliant = $false
                $IsCompliant = $false
            } #end if

            #add results of current computer to final result
            if (-not $IsCompliant) {
                $compObj += New-Object -TypeName psobject -Property @{Computername   = $policy.ComputerName
                                                                       IsCompliant    = $IsCompliant
                                                                       PassCompliance = $obj
                                                                      } #close prop
            } #end if
        } #end foreach

        $compObj
    } #end process
    End
    {
    }
}

<#
.Synopsis
  Formats and Email with Password Audit of a Server or to the console in a pretty format.
.DESCRIPTION
  For servers that are not in compliance with the password policies
  and lockout policies, will send an email with the names of the
  attributes that are out of compliance
.PARAMETER To
  Recipient of the compliance results
.PARAMETER SMTP
  SMTP Server used to send email
.PARAMETER FailedComputers
  Custom object containing computers and their password compliance results
.NOTES
  Sends to console in a pretty format. To keep results in objects, use Test-PasswordAuditResults
.EXAMPLE
  Send-PasswordCompliance -To jerry.seinfeld@mycompany.com -CompObj $Comps -SMTP smtp.mycompany.com
#>

function Send-PasswordCompliance
{
    [CmdletBinding()]
    param
    (
        # email address to send to
        [Parameter(Mandatory=$false, 
                   ValueFromPipelineByPropertyName=$true,
                   ParameterSetName='Email')]
        $To,

        # SMTP Server
        [Parameter(Mandatory=$false, 
                   ValueFromPipelineByPropertyName=$true,
                   ParameterSetName='Email')]
        $SMTP,

        # Computer objects with compliance results
        [Parameter(Mandatory=$true, 
                   ValueFromPipelineByPropertyName=$true)]
        $FailedComputers
    )
        Begin
        {
          
        }

        Process
        {
            #create body of email
            $complianceReport = @"
         ServerResults Compliance
--------------------------- ---------------
 
"@

            #add only items that are not in compliance to report. Parse hash table
            foreach ($computer in $FailedComputers)
            {
                #format here string
                $complianceReport += $computer.Computername
                if (-not ($computer.PassCompliance)['PassMinAgeDaysCompliant'])        { $complianceReport += "`nPassMinAgeDaysCompliant $(($computer.PassCompliance)['PassMinAgeDaysCompliant'])"}
                if (-not ($computer.PassCompliance)['PassMaxAgeDaysCompliant'])        { $complianceReport += "`nPassMaxAgeDaysCompliant $(($computer.PassCompliance)['PassMaxAgeDaysCompliant'])"}
                if (-not ($computer.PassCompliance)['ObservationWindowMinsCompliant']) { $complianceReport += "`nObservationWindowMinsCompliant $(($computer.PassCompliance)['ObservationWindowMinsCompliant'])"}
                if (-not ($computer.PassCompliance)['PassLengthCompliant'])            { $complianceReport += "`nPassLengthCompliant $(($computer.PassCompliance)['PassLengthCompliant'])"}
                if (-not ($computer.PassCompliance)['FailedLogonsAllowedCompliant'])   { $complianceReport += "`nFailedLogonsAllowedCompliant $(($computer.PassCompliance)['FailedLogonsAllowedCompliant'])"}
                if (-not ($computer.PassCompliance)['PassHistoryCountCompliant'])      { $complianceReport += "`nPassHistoryCountCompliant $(($computer.PassCompliance)['PassHistoryCountCompliant'])"}
                if (-not ($computer.PassCompliance)['PassUnlockoutMinsCompliant'])     { $complianceReport += "`nPassUnlockoutMinsCompliant $(($computer.PassCompliance)['PassUnlockoutMinsCompliant'])"}
                $complianceReport += "`n`n"
            } #end foreach

            switch ($pscmdlet.ParameterSetName) {
                'Email' {
                    Send-MailMessage -Body $complianceReport -SmtpServer $SMTP -To $To -From noreply@mycompany.com -Subject "Failed Password Policies"
                    break
                } #end email
                Default { $complianceReport } #sends to console in a pretty format. To keep in objects, use Test-PasswordAuditResults
            }
            
    } #end Process
    End
    {

    }
} #end Send-PasswordCompliance