function Get-TerminatedComputersReport { <# .SYNOPSIS Generates a report of computers that have not "logged in" in the last n days. .DESCRIPTION Generates a report of computers that have not "logged in" in the last n days. If an AWS Profile is provided it will also compare against EC2 instances to attempt to validate a confidence level (0 - 4) of termination, 0 indicating it is almost guaranteed to be an active system and 4 meaning almost guaranteed to be inactive and "terminated". If not compared against AWS termination confidence will be 0 or 1. A 1 indicates that the machine password has not been changed in at least 90 days. .PARAMETER Domain If providing credentials for an alternate domain, specify the domain. .PARAMETER Credential Used when connecting to a non-native domain to generate a report. .PARAMETER OU Optional parameter, specify the search base for AD computer objects. Example: "OU=FH Computers,DC=FH,DC=local" .PARAMETER NumberOfDays The cut off value for the last logon filter, defaults to 120 days. .PARAMETER CheckAWS Will compare AD to AWS instances for increased accuracy of termination confidence. .OUTPUTS [pscustomobject] .EXAMPLE New-TerminatedComputersReport -Domain fh.local -Credential $credential -OU "OU=POD0,OU=FH Computers,DC=fh,DC=local" -NumberOfDays 90 This example retrieves all computer objects in the POD0 OU that have not logged on in preceeding 90 days, a [pscredential] was created and passed in as $credential .EXAMPLE New-TerminatedComputersReport -Domain fh.local -Credential $credential -OU "OU=POD0,OU=FH Computers,DC=fh,DC=local" -NumberOfDays 90 -CheckAWS This example retrieves all computer objects in the POD0 OU that have not logged on in preceeding 90 days, then retrieves EC2 instance information. A comparison of Name and IP is done to determine a termination confidence score, which is noted in the output. A [pscredential] was created and passed in as $credential. .EXAMPLE New-TerminatedComputersReport -Domain fh.local -Credential $credential -OU "OU=POD0,OU=FH Computers,DC=fh,DC=local" -NumberOfDays 90 -CheckAWS | Export-Csv -Path $env:TEMP\TerminatedComputers.csv -NoTypeInformation This example retrieves all computer objects in the POD0 OU that have not logged on in preceeding 90 days, then retrieves EC2 instance information. It is then piped to Export-Csv for later review. #> [cmdletbinding(DefaultParameterSetName = "Default")] param( [parameter(ParameterSetName = "Credentialed", Mandatory = $true)] [string] [ValidateSet("fh.local", "corp.alkamitech.com")] $Domain, [parameter(ParameterSetName = "Credentialed", Mandatory = $true)] [pscredential] $Credential, [parameter(ParameterSetName = "Credentialed", Mandatory = $false)] [parameter(ParameterSetName = "Default", Mandatory = $false)] [ValidateNotNullorEmpty()] [string] $OU, [parameter(ParameterSetName = "Credentialed", Mandatory = $false)] [parameter(ParameterSetName = "Default", Mandatory = $false)] [uint16] [ValidateRange(1, 365)] $NumberOfDays = 120, [parameter(ParameterSetName = "Credentialed", Mandatory = $false)] [parameter(ParameterSetName = "Default", Mandatory = $false)] [switch] $CheckAWS ) $outputObjects = [System.Collections.Generic.List[System.Object]]::new() $logLead = Get-LogLeadName # Make sure AWS Powershell modul is loaded, if needed if ($PSBoundParameters.ContainsKey("CheckAWS")) { # Set up some sorting buckets $ec2Instances = [System.Collections.Generic.List[System.Object]]::new() $ec2ObjectIndexByIP = [System.Collections.Specialized.OrderedDictionary]::new() $ec2ObjectIndexByName = [System.Collections.Specialized.OrderedDictionary]::new() $ec2IPIndex = 0 $ec2NameIndex = 0 # Check if this is running in TeamCity, set accounts as appropriate if ((Test-IsTeamCityProcess)) { $awsAccounts = "prod", "dev", "qa", "corp" } else { $awsAccounts = "temp-prod", "temp-dev", "temp-qa", "temp-corp" } # Make sure AWSPowerShell is loaded/can be loaded if (!((Get-Module).Name -contains "AWSPowerShell")) { try { Import-Module AWSPowerShell } catch { Write-Error "$logLead : Unable to load the AWSPowerShell module" return } } } # If we are specifying credentials we need to make sure we connect to the correct domain for operations if ($PSBoundParameters.ContainsKey("Credential")) { try { $fastDomainController = ((Resolve-DnsName $Domain -ErrorAction Stop).IPAddress | ForEach-Object { Test-Connection $_ -Count 1 -ErrorAction SilentlyContinue } | Sort-Object ResponseTime)[0].Address } catch { Write-Error "$logLead : Error when resolving DNS for $Domain" return } } $date = (Get-Date).AddDays(-$NumberofDays) # Build a query for AD $adComputersSplat = @{} $adComputersSplat += @{"Filter" = { lastLogonDate -lt $date } } $adComputersSplat += @{"Properties" = @("lastLogonDate", "PasswordLastSet", "Name", "IPv4Address") } if ($PSBoundParameters.ContainsKey("Credential")) { $adComputersSplat += @{"Credential" = $Credential } $adComputersSplat += @{"Server" = $fastDomainController } } if ($PSBoundParameters.ContainsKey("OU")) { $adComputersSplat += @{"SearchBase" = $OU } } # Get AD Computer objects that match our filter Write-Progress -Activity "Generating Report" -Status "Retrieving AD Objects" -Id 1 -PercentComplete 25 try { $adComputers = Get-ADComputer @adComputersSplat } catch [System.Security.Authentication.AuthenticationException] { Write-Error "$logLead : Unable to connect to the target domain, invalid credentials." return } catch { Write-Error "$logLead : Unable to retrieve Computer objects from AD." return } if ($PSBoundParameters.ContainsKey("CheckAWS")) { # Retrieve EC2 data Write-Progress -Activity "Generating Report" -Status "Retrieving EC2 Data" -Id 1 -PercentComplete 50 $incrementPercent = 60 foreach ($awsAccount in $awsAccounts) { Write-Progress -Activity "Generating Report" -Status "Retrieving EC2 Data from $($awsAccount)" -Id 1 -PercentComplete $incrementPercent try { $ec2Instances += (Get-EC2Instance -ProfileName $AWSAccount -Region us-east-1).Instances } catch { Write-Error "$logLead : An error occurred while retrieving EC2 instances in us-east-1 for account $($awsAccount)." } if ($awsAccount -like "*prod*") { try { $ec2Instances += (Get-EC2Instance -ProfileName $AWSAccount -Region us-west-2).Instances } catch { Write-Error "$logLead : An error occurred while retrieving EC2 instances in us-west-2 for account $($awsAccount)." } } $incrementPercent += 5 } # Build some indexes to speed things up later foreach ($instance in $ec2Instances) { Write-Progress -Activity "Generating Report" -Status "Retrieving EC2 Data" -Id 1 -PercentComplete ($incrementPercent + 5) # Index by IPv4 Address if ($instance.PrivateIpAddress) { $ec2ObjectIndexByIP.Add($instance.PrivateIpAddress, $ec2IPIndex) } $ec2IPIndex++ } foreach ($instance in $ec2Instances) { # Index by the assigned Hostname if ($instance.tags.key -contains "alk:hostname") { $name = ($instance.tags | Where-Object { $_.Key -eq "alk:hostname" }).Value.ToLower() # There are some duplicate hostnames, this will append the index number to avoid throwing an error if (!($ec2ObjectIndexByName.Contains($name))) { $ec2ObjectIndexByName.Add($name, $ec2NameIndex) } else { $name = $name + $ec2NameIndex $ec2ObjectIndexByName.Add($name, $ec2NameIndex) } } $ec2NameIndex++ } # Now that we have indexes let's see if we have matching EC2 Instances foreach ($computer in $adComputers) { try { $nameIndex = $ec2ObjectIndexByName[$computer.Name.ToLower()] } catch { $nameIndex = $null } try { $ipIndex = $ec2ObjectIndexByIP[$computer.IPv4Address] } catch { $ipIndex = $null } if (!($null -eq $nameIndex)) { $instanceByName = $ec2Instances[$nameIndex] } else { $instanceByName = $null } if (!($null -eq $ipIndex)) { $instanceByIPv4 = $ec2Instances[$ipIndex] } else { $instanceByIPv4 = $null } # Compare results if (!($null -eq $ipIndex) -or !($null -eq $nameIndex)) { $ec2NameMatchesIP = (($instanceByName.InstanceId) -eq ($instanceByIPv4.InstanceId)) } else { $ec2NameMatchesIP = $false } # If things look to be lining up lets try and get the designation number if it exists if ($ec2NameMatchesIP -eq $true) { if ($instanceByName.tags.key -contains "alk:designation") { $designation = ($instanceByName.tags | Where-Object { $_.Key -eq "alk:designation" }).Value } } else { $designation = $null } # Build a custom object $tempObj = New-Object -TypeName PSObject # Store values $tempObjProps = [ordered]@{ "ADName" = $computer.Name "IsEnabled" = $computer.Enabled "ADIPv4Address" = $computer.IPv4Address "LastLogon" = $computer.lastLogonDate "PasswordLastSet" = $computer.PasswordLastSet "SID" = $computer.SID "InstanceIdByIP" = ($instanceByIPv4.InstanceId) "InstanceIdByName" = ($instanceByName.InstanceId) "DoesInstanceMatch" = $ec2NameMatchesIP "designation" = $designation "TerminationConfidence" = [byte](($null -eq $instanceByIPv4) + ($null -eq $instanceByName) + !($ec2NameMatchesIP) + ($computer.PasswordLastSet -lt (Get-Date).AddDays(-90))) } # Add custom properties $tempObj | Add-Member -NotePropertyMembers $tempObjProps -TypeName ComputerObject $outputObjects.Add($tempObj) } } else { Write-Progress -Activity "Generating Report" -Id 1 -PercentComplete 95 foreach ($computer in $adComputers) { # Build a custom object $tempObj = New-Object -TypeName PSObject # Store values $tempObjProps = [ordered]@{ "ADName" = $computer.Name "IsEnabled" = $computer.Enabled "ADIPv4Address" = $computer.IPv4Address "LastLogon" = $computer.lastLogonDate "PasswordLastSet" = $computer.PasswordLastSet "SID" = $computer.SID "TerminationConfidence" = [byte]($computer.PasswordLastSet -lt (Get-Date).AddDays(-90)) } # Add custom properties $tempObj | Add-Member -NotePropertyMembers $tempObjProps -TypeName ComputerObject $outputObjects.Add($tempObj) } } Write-Progress -Activity "Generating Report" -Status "Finished" -Id 1 -Completed return $outputObjects }