ps/Modules/Alkami.DevOps.SystemEngineering/Public/Get-TerminatedComputersReport.ps1
2023-05-30 22:51:22 -07:00

272 lines
12 KiB
PowerShell

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
}