Export-LastLogonTime.ps1


<#PSScriptInfo
 
.VERSION 1.4
 
.GUID efd8653f-3a23-4d78-a7d1-b766da9015bf
 
.AUTHOR Chris Carter
 
.COMPANYNAME
 
.COPYRIGHT 2016 Chris Carter
 
.TAGS ActiveDirectory, LastLogonTime
 
.LICENSEURI http://creativecommons.org/licenses/by-sa/4.0/
 
.PROJECTURI https://gallery.technet.microsoft.com/Export-Last-Logon-Times-4fcb07cb
 
.ICONURI
 
.EXTERNALMODULEDEPENDENCIES ActiveDirectory
 
.REQUIREDSCRIPTS
 
.EXTERNALSCRIPTDEPENDENCIES
 
.RELEASENOTES The OrganizationalUnit parameter will now accept a distinguished name as well as the name. This command will now also handle OUs with the same name.
 
 
#>


<#
.SYNOPSIS
This command will export the account name and the last logon time of the users in the specified OU to a .csv file format at the specified destination.
 
.DESCRIPTION
Export-LastLogonTime takes the OU name or distinguished name specified in the OU parameter and retrieves its users' account names and last logon times. Then it exports a .csv file to the destination given in the Destination parameter. This script will search the entire domain for the OU name specified. If the destination path contains spaces it must be wrapped in quotation marks, and the file name specified must end in .csv.
  
Due to the common problem of the LastLogon not replicating between domain controllers, this script will search for domain controllers and compare the LastLogon from each one to find the most recent time a user logged in.
.PARAMETER OrganizationalUnit
Specifies the name or the distinguished name of the OU from which to retrieve users. Any escape characters are still required to be escaped: http://social.technet.microsoft.com/wiki/contents/articles/5312.active-directory-characters-to-escape.aspx
 
.PARAMETER Destination
Specifies the location and file name of the exported csv file. If you do not specify a full path, the current location will be used. The file name must have a .csv extension specified.
 
.INPUTS
None. You cannot pipe objects to Export-LastLogonTimes.ps1.
 
.OUTPUTS
None. Export-LastLogonTimes.ps1 does not generate any output.
 
.EXAMPLE
The following command will get the account name and last logon times for the OU named Users and export the information to a .csv file named LastLogon.csv in the Administrator's Documents folder.
 
PS C:\> Export-LastLogonTime -OU Users -Destination "C:\Users\administrator\Documents\LastLogon.csv"
.EXAMPLE
PS C:\> 'Users', 'OU=Restricted Users,DC=example,DC=com' | Export-LastLogonTime -Destination "C:\Users\administrator\Documents\LastLogon.csv"
 
This command will export the last logon times of all the users in the Users and Restricted Users OUs. Note that Restricted Users is given as its distinguished name, while Users is not. The command will accept either.
.NOTES
This script uses the ActiveDirectory PowerShell Module. This module is automatically installed on domain controllers and workstations or member servers that have installed the Remote Server Administration Tools (RSAT). If you are not on a machine that meets this criteria, the script will fail to work.
 
.LINK
Get-ADUser
.LINK
Get-ADObject
.LINK
Get-ADDomainController
.LINK
Export-Csv
#>


#Requires -Version 3.0
[CmdletBinding(HelpURI='https://gallery.technet.microsoft.com/Export-Last-Logon-Times-4fcb07cb')]

Param(
    [Parameter(Mandatory=$true, Position=0,ValueFromPipeline=$true)]
    [Alias("OU")]
        [String[]]$OrganizationalUnit,

    [Parameter(Mandatory=$true, Position=1)]
    [ValidatePattern("\.csv$")]
        [String]$Destination
)

Begin {
    #Function to test for module installation and successful load. Thank you to Hey, Scripting Guy! blog for this one.
    Function Test-Module {
        Param (
            [Parameter(Mandatory=$true, Position=0)][String]$Name
        )

        #Test for module imported
        if (!(Get-Module -Name $Name)) {
            #Test for module availability
            if (Get-Module -ListAvailable | Where-Object {$_.Name -eq $Name}) {
                #If not loaded but available, import module
                Import-Module $Name
                $True
            }
            #Module not installed
            else {$False}
        }
        #Module already imported
        else {$True}
    }

    Function Get-LogonTimes($OU) {
        if ($OU -imatch '(?:(?:(?:OU|CN)=.+?)(?<!\\),)+?(?:(?:DC=.+?)(?<!\\),)?DC=.+$') {
            Write-Verbose "Distinguished Name entered"
            $OUDN = $OU
        }
        else {
            #Search for input OU and store distinguished name property
            Write-Verbose "Resolving Distinguished Names"
            $OUDN = (Get-ADOrganizationalUnit -Filter "Name -eq '$OU'").DistinguishedName
        }

        #test for valid result of OU
        if ($OUDN) {
            #Iterate through in case multiple OUs have the same name
            foreach ($DN in $OUDN) {
                Write-Verbose "Getting users of OU $DN..."
                #If distinguished name exists, get users' account name and last logon times
                $users = Get-ADUser -Filter * -SearchBase $DN -Properties DisplayName

                #Iterate through each user and get its LastLogonDate property from each domain controller
                foreach ($user in $users) {
                    Write-Verbose "Getting logon times for user $($user.Name)..."
                    $DCLogonTimes = @()
                    foreach ($dc in $DCs) {
                        Write-Verbose "Logon times from DC $($dc.Name)..."
                        $DCLogonTimes += (Get-ADUser -Identity $user.SamAccountName -Server $dc.Name -Properties LastLogonDate).LastLogonDate
                    }
                    #Sort the dates to get the highest date on top
                    Write-Verbose "Getting the latest time of all logons...`n`n"
                    $DCLogonTimes = $DCLogonTimes | Sort-Object -Descending
                    [PSCustomObject]@{SamAccountName = $user.SamAccountName; DisplayName = $user.DisplayName; LastLogonDate = $DCLogonTimes[0]}
                }
            }
        }
        #No OU was found with the name supplied
        else {
            #Generate alert for no OU found
            Write-Error "The OU you specified, $OU, is not a valid OU name in your domain."
        }
    }

    #Store list of logon times
    $result = @()

    #Test for ActiveDirectory Module
    Write-Verbose "Testing for ActiveDirectory Module..."
    if (!(Test-Module -Name "ActiveDirectory")) {
        #If not installed, alert
        Write-Error "There was a problem loading the Active Directory module. Either you are not on a domain controller or your workstation does not have Remote Server Administration Tools (RSAT) installed."
        exit
    }
    Write-Verbose "ActiveDirectory Module installed"

    #Get Domain Controllers - this can take a while
    #Use ArrayList so the Remove method can be used
    Write-Verbose "Getting Domain Controllers..."
    [System.Collections.ArrayList]$DCs = Get-ADDomainController -Filter *
    Write-Verbose "Domain Controllers received"

    #Test that the dcs are running AD Web Services, and remove from the list if they are not.
    #Clone because objects can't be removed while enumerating
    Write-Verbose "Testing that all Domain Controllers are up and running Active Directory Web Services..."
    foreach ($dc in $DCs.Clone()) {
        if (!(Get-Service -ComputerName $dc.Name -Name ADWS -ea SilentlyContinue)) {
            $DCs.Remove($dc)
            Write-Warning "The Domain Controller $($dc.Name) is either down, or does not have Active Directory Web Services running. It will be skipped during checking."
        }
    }
    Write-Verbose "All eligible Domain Controllers added to the list."
}

Process {
    foreach ($OU in $OrganizationalUnit) {
        $result += Get-LogonTimes -OU $OU
    }
}

End {
    Write-Verbose "Getting the results to export"
    #Sort and export the desired information to a .csv file
    $result = $result | Sort-Object -Property SamAccountName
    $result | Export-Csv -Path $Destination -NoTypeInformation
    Write-Verbose "Results exported"
}