View difference between Paste ID: YgP5ZL7G and EJ0vFc6z
SHOW: | | - or go back to the newest paste.
1
####################################################
2
#
3
# AD account cleanup script
4
# Date Created : 9/22/17
5
# Author : PowerMonkey500
6
#
7
####################################################
8
#
9
# WARNING: THIS SCRIPT WILL OVERWRITE EXTENSIONATTRIBUTE3 FOR INACTIVE USERS, MAKE SURE YOU ARE NOT USING IT FOR ANYTHING ELSE
10
# This script is SLOW because it gets the most accurate last logon possible by comparing results from all DCs. By default the lastlogontimestamp is only replicated every 14 days minus a random percentage of 5.
11
#
12
####################################################
13
14
#FUNCTION DECLARATIONS
15
#===================================================================================
16
17
#Logging function - starts transcript and cleans logs older than specified retention date.
18
Function Start-Logging{
19
    param (
20
        [Parameter(Mandatory=$true)][String]$LogDirectory,
21
        [Parameter(Mandatory=$true)][String]$LogName,
22
        [Parameter(Mandatory=$true)][Int]$LogRetentionDays
23
        )
24
25
    #Sets screen buffer from 120 width to 500 width. This stops truncation in the log.
26
    $ErrorActionPreference = 'SilentlyContinue'
27
    $pshost = get-host
28
    $pswindow = $pshost.ui.rawui
29
 
30
    $newsize = $pswindow.buffersize
31
    $newsize.height = 3000
32
    $newsize.width = 500
33
    $pswindow.buffersize = $newsize
34
 
35
    $newsize = $pswindow.windowsize
36
    $newsize.height = 50
37
    $newsize.width = 500
38
    $pswindow.windowsize = $newsize
39
    $ErrorActionPreference = 'Continue'
40
41
    #Create log directory if it does not exist already
42
    If (!(Test-Path $LogDirectory)){mkdir $LogDirectory}
43
44
    #Starts logging.
45
    New-Item -ItemType directory -Path $LogDirectory -Force | Out-Null
46
    $Today = Get-Date -Format M-d-y
47
    Start-Transcript -Append -Path ($LogDirectory + "\" + $LogName + "." + $Today + ".log") | Out-Null
48
49
    #Shows proper date in log.
50
    Write-Output ("Start time: " + (Get-Date))
51
52
    #Purges log files older than X days
53
    $RetentionDate = (Get-Date).AddDays(-$LogRetentionDays)
54
    Get-ChildItem -Path $LogDirectory -Recurse -Force | Where-Object { !$_.PSIsContainer -and $_.CreationTime -lt $RetentionDate } | Remove-Item -Force
55
} 
56
57
###NOT USED IN SCRIPT, JUST FOR UTILITY WHEN TWEAKING###
58
Function Get-ADUserLastLogon ([string]$UserName){
59
    $dcs = Get-ADDomainController -Filter {Name -like "*"}
60
      $time = 0
61
      $dt = 0
62
        foreach($dc in $dcs) {
63
        Try
64
        {
65
            $user = Get-ADUser -Server $dc -identity $userName -properties LastLogon,LastLogonTimestamp
66
            if($user.LastLogonTimeStamp -gt $time)
67
            {
68
                $time = $user.LastLogonTimeStamp
69
            }
70
            if ($user.LastLogon -gt $time)
71
            {
72
                $time = $user.LastLogon
73
            }   
74
        }
75
        Catch {}
76
    }
77
    $dt = [DateTime]::FromFileTime($time)
78
79
    $OutputObj = New-Object -TypeName PSObject
80
    $OutputObj | Add-Member -MemberType NoteProperty -Name SamAccountName -Value $user.SamAccountName
81
    $OutputObj | Add-Member -MemberType NoteProperty -Name Enabled -Value $user.Enabled
82
    $OutputObj | Add-Member -MemberType NoteProperty -Name LastLogon -Value $dt
83
    $OutputObj | Add-Member -MemberType NoteProperty -Name GivenName -Value $user.GivenName
84
    $OutputObj | Add-Member -MemberType NoteProperty -Name Surname -Value $user.SurName
85
    $OutputObj | Add-Member -MemberType NoteProperty -Name Name -Value $user.Name
86
    $OutputObj | Add-Member -MemberType NoteProperty -Name DistinguishedName -Value $user.DistinguishedNameName
87
88
    Return $OutputObj
89
}
90
91
#-----------------------------------
92
#Main script
93
#-----------------------------------
94
#Declarations
95
$From = "noreply@email.com"
96
$SMTPServer = "server.domain.local"
97
$To = @('email1@email.com','email2@email.com') #Array. You can add more than one entry.
98
$Subject = "Account Cleanup Report"
99
$TryCount = 0 #Leave this at 0.
100
$LogDirectory = "C:\Disable-InactiveADAccountsLog" #No trailing slash
101
$LogName = "Disable-InactiveADAccountsLog"
102
$MaxTryCount = 20 #Amount of times to try for identical DC results before giving up. 30 second retry delay after each failure.
103
$UTCSkew = -5 #Accounting for the time zone difference, since some results are given in UTC. Eastern time is UTC-5. 
104
$ExclusionsGroup = "ServiceAccts" #AD group containing accounts to exclude.
105
$DaysThreshold = 30 #Threshold of days of inactivity before disabling the user.
106
107
#Start logging
108
Start-Logging -logdirectory $LogDirectory -logname $LogName -LogRetentionDays 30
109
110
#Get all DCs, add array names to vars array
111
$DCnames = @()
112
If (!$DCs){$DCs = Get-ADGroupMember 'Domain Controllers'}
113
$DCs | % {$DCnames += $_.Name}
114
115
#Check that results match from each DC
116
While (($ComparisonResults -contains $False -or !$ComparisonResults) -and $TryCount -lt $MaxTryCount){
117
#Fetch AD users from each DC, add to named array
118
    $DCnames | % {
119
        #Filters / Exclusions. 
120
        Write-Output ("Fetching last logon times from " + $_ + "...")
121
        New-Variable -Name $_ -Value (Get-ADUser -Filter {Enabled -eq $True} -Server $_ -Properties DistinguishedName,LastLogon,LastLogonTimestamp,whenCreated,Description | Sort SamAccountName) -Force
122
    } 
123
124
    $ComparisonResults = @()
125
    ForEach ($i in 0..(($DCnames.Count)-1)){
126
        If ($i -le (($DCnames.Count)-2)){
127
            Write-Output ("Comparing results from " + $DCnames[$i] + " and " + $DCnames[$i+1] + "...")
128
            $NotEqual = Compare-Object (Get-Variable -Name $DCnames[$i]).Value (Get-Variable -Name $DCnames[$i+1]).Value -Property SamAccountName
129
130
            If (!$NotEqual) {$ComparisonResults += $True}
131
            Else {$ComparisonResults += $False}
132
        }
133
    }
134
    If ($ComparisonResults -contains $False){
135
        Write-Warning "One or more DCs returned differing results. This is likely just replication delay. Retrying..."
136
        $TryCount++
137
        Start-Sleep 30
138
    }
139
}
140
If ($TryCount -lt $MaxTryCount){Write-Output "All DC results are identical!"}
141
Else {Throw "Try limit exceeded. Aborting."}
142
143
#Get current time for comparison later. 
144
If (!$StartTime){$StartTime = Get-Date}
145
146
#User count so we know how many times to loop.
147
$UserCount = (Get-Variable -Name $DCnames[0]).Value.Count
148
149
#Create results array of the same size
150
$FullResults = @($null) * $UserCount
151
152
#Loop through array indexes
153
ForEach ($i in 0..($UserCount -1)){
154
    #Grab user object from each resultant array, make array of each user object
155
    $UserEntries = @(0) *$DCnames.Count
156
    ForEach ($o in 0..($DCnames.Count -1)) {
157
        $UserEntries[$o] = (Get-Variable -Name $DCnames[$o]).Value[$i]
158
    }
159
    If (($UserEntries.SamAccountName | Select -Unique).Count -gt 1){Throw "A user mismatch at index $i has occurred. Aborting."}
160
161
    #Find most recent LastLogon, whenCreated, and LastLogonTimestamp.
162
    If ($UserEntries.LastLogon){
163
        [datetime]$LastLogon = [datetime]::FromFileTimeUtc(($UserEntries | Measure-Object -Property LastLogon -Maximum).Maximum)
164
        [datetime]$TrueLastLogon = $LastLogon
165
    }
166
    Else {[datetime]$LastLogon = 0; $TrueLastLogon = 0} 
167
168
    If ($UserEntries.whenCreated){
169
        [datetime]$whenCreated = $UserEntries[0].whenCreated
170
    }
171
    Else {[datetime]$whenCreated = 0}
172
173
    If ($UserEntries.LastLogonTimestamp){
174
        [datetime]$LastLogonTimestamp = [datetime]::FromFileTimeUtc(($UserEntries | Measure-Object -Property LastLogonTimestamp -Maximum).Maximum)
175
    }
176
    Else {[datetime]$LastLogonTimestamp = 0}
177
178
    #If LastLogonTimestamp is newer, use that instead of LastLogon.
179
    If ($LastLogonTimestamp -gt $LastLogon){$TrueLastLogon = $LastLogonTimestamp}
180
181
    #UTC to EST
182
    If ($TrueLastLogon -ne 0){$TrueLastLogon = $TrueLastLogon.AddHours($UTCSkew)}
183
184
    #If TrueLastLogon is older than 20 years (essentially null/zero), set to true zero
185
    If ((New-TimeSpan -Start $TrueLastLogon -End $StartTime).Days -gt 7300){[string]$TrueLastLogon = $null}
186
187
    #Calculate days of inactivity.
188
    If ($TrueLastLogon -ne $null -and $TrueLastLogon -ne ""){$DaysInactive = (New-TimeSpan -Start $TrueLastLogon -End $StartTime).Days}
189
    Else {$DaysInactive = (New-TimeSpan -Start $whenCreated -End $StartTime).Days}
190
191
    #Create object for output array
192
    $OutputObj = New-Object -TypeName PSObject
193
    $OutputObj | Add-Member -MemberType NoteProperty -Name SamAccountName -Value $UserEntries[0].SamAccountName
194
    $OutputObj | Add-Member -MemberType NoteProperty -Name Enabled -Value $UserEntries[0].Enabled
195
    $OutputObj | Add-Member -MemberType NoteProperty -Name LastLogon -Value $TrueLastLogon
196
    $OutputObj | Add-Member -MemberType NoteProperty -Name whenCreated -Value $whenCreated
197
    $OutputObj | Add-Member -MemberType NoteProperty -Name DaysInactive -Value $DaysInactive
198
    $OutputObj | Add-Member -MemberType NoteProperty -Name GivenName -Value $UserEntries[0].GivenName
199
    $OutputObj | Add-Member -MemberType NoteProperty -Name Surname -Value $UserEntries[0].SurName
200
    $OutputObj | Add-Member -MemberType NoteProperty -Name Name -Value $UserEntries[0].Name
201
    $OutputObj | Add-Member -MemberType NoteProperty -Name DistinguishedName -Value $UserEntries[0].DistinguishedName
202
    $OutputObj | Add-Member -MemberType NoteProperty -Name Description -Value $UserEntries[0].Description
203
204
    #Append object to output array and output preogress to console.
205
    $FullResults[$i] = $OutputObj
206
    $PercentComplete = [math]::Round((($i/$UserCount) * 100),2)
207
    Write-Output ("User: " + $OutputObj.SamAccountName + " - Last logon: $TrueLastLogon ($DaysInactive day(s) inactivity) - $PercentComplete% complete.")
208
}
209
210
Write-Output "Getting exclusion group members..."
211
$UserExclusions = (Get-ADGroupMember -Identity $ExclusionsGroup).SamAccountName
212
213
#Splits "other" and "real" users into two different arrays.
214
Write-Output "Filtering users..."
215
$RealUsersResults = @()
216
$RealUsersResults = $FullResults | Where {$UserExclusions -notcontains $_.SamAccountName}
217
218
$OtherUsersResults = @()
219
$FullResults = $FullResults | Where {$_ -ne $null}
220
221
#For some reason compare-object is not working properly without specifying all properties. Don't know why. 
222
$OtherUsersResults = Compare-Object $RealUsersResults $FullResults `
223
-Property SamAccountName,enabled,lastlogon,whencreated,DaysInactive,givenname,surname,name,distinguishedname,Description | `
224
select SamAccountName,enabled,lastlogon,whencreated,DaysInactive,givenname,surname,name,distinguishedname,Description
225
226
#Disable accounts with inactivity past days threshold, add to UsersDisabled array for CSV report
227
$UsersDisabled = @()
228
$RealUsersResults | % {
229
    If ($_.DaysInactive -ge $DaysThreshold){
230
        Write-Output ("Disabling " + $_.SamAccountName + "...")
231
        Disable-ADAccount -Identity $_.SamAccountName
232
        $Date = "INACTIVE SINCE " + $_.LastLogon
233
        Set-ADUser -Identity $_.SamAccountName -Replace @{ExtensionAttribute3=$Date}
234
        $UsersDisabled += $_
235
    }
236
}
237
238
#Filtered users - add to UsersNotDisabled array for CSV report
239
$OtherInactiveUsers = @()
240
$OtherUsersResults | % {
241
    If ($_.DaysInactive -ge $DaysThreshold){
242
        $OtherInactiveUsers += $_
243
    }
244
}
245
246
#Reports "Auto-Disable Exclusions-ENT" members
247
$ExcludedUsersReport = $FullResults | Where {$UserExclusions -contains $_.SamAccountName} | Select * -ExcludeProperty Enabled,LastLogon,whenCreated,DaysInactive
248
249
#Export CSVs to logging directory
250
$UsersDisabledCSV = $LogDirectory + "\InactiveUsers-Disabled.csv"
251
$UsersNotDisabledCSV = $LogDirectory + "\InactiveUsers-Excluded.csv"
252
$ExcludedUsersReportCSV = $LogDirectory + "\Auto-Disable Exclusions.csv"
253
$UsersDisabled | Export-CSV $UsersDisabledCSV -NoTypeInformation -Force
254
$OtherInactiveUsers | Export-CSV $UsersNotDisabledCSV -NoTypeInformation -Force
255
$ExcludedUsersReport | Export-CSV $ExcludedUsersReportCSV -NoTypeInformation -Force
256
257
<#
258
Write-Output "Starting Move-Disabled task..."
259
Start-ScheduledTask -TaskName "\Move-Disabled"
260
#>
261
262
#Send email with CSVs as attachments
263
Write-Output "Sending email..."
264
Send-MailMessage -Attachments @($UsersDisabledCSV,$UsersNotDisabledCSV,$ExcludedUsersReportCSV)`
265
 -From $From -SmtpServer $SMTPServer -To $To -Subject $Subject
266
267
Stop-Transcript