Get-AllComputerAccounts.ps1


Description

@GitHub Copilot - Welcome @BanterBoy, I’m your Copilot and I’m here to help you get things done faster. I can identify issues, explain and even improve code.

You can ask generic questions, but what I’m really good at is helping you with your code. For example:

Generate unit tests for my code Explain the selected code Propose a fix for the bugs in my code If you want to learn more about my capabilities and limitations, check out the Copilot documentation.

I’m powered by AI, so surprises and mistakes are possible. Make sure to verify any generated code or suggestions, and share feedback so that we can learn and improve.

@BanterBoy: - Explain what the script does and why it is useful.

@GitHub Copilot -


Script

<#
  This script will create a report of all computer accounts. It helps
  idenify all domain joined servers or workstations in your environment
  for licensing and reporting requirements, including any potentially
  stale Computer accounts that can be deleted.

  Note that for servers we filter out Cluster Name Objects (CNOs) and
  Virtual Computer Objects (VCOs) by checking the objects serviceprincipalname
  property for a value of MSClusterVirtualServer. The CNO is the cluster
  name, whereas a VCO is the client access point for the clustered role.
  These are not actual computers, so we exlude them to assist with
  accuracy.

  A cluster updates the lastLogonTimeStamp of the CNO/VNO when it brings
  a clustered network name resource online. So it could be running for
  months without an update to the lastLogonTimeStamp attribute.

  For servers it reports on whether or not it's a virtual machine, and
  which hypervisor it's running on.

  The CSV report has 18 columns, 7 of which are optional depending on
  the script variables you set.
  - ComputerDomain - Domain name of the computer. A handy column when auditing multiple domains and merging spread sheets.
  - ComputerName  Needs no explanation.
  - OperatingSystem  The Operating System registered in Active Directory.
  - IsVirtual - Validates if the server is virtual.
  - Hypervisor - The hypervisor the virtual machine is running on.
  - AccountEnabled  If the AD account is enabled.
  - IsPingable  If it's pingable (AKA alive). Will be accurate unless a firewall or access list is blocking it.
  - PasswordLastChanged  When the computer password was last changed. Should typically never go beyond 90 days.
  - StaleAccount  is derived from 3 values:
                   1. PasswordLastChanged  > 90 days ago
                   2. LastLogonDate > 30 days ago
                   3. IsPingable = False
  - LastLogonDate - The lastLogonTimeStamp attribute from Active Directory, which can be up to 14 days out.
  - RealLastLogonDate  An accurate date and time when it last logged on (authenticated). This get the lastLogon attribute from each Domain Controller.
  - LastLogonDC  The Domain Controller it last authenticated against, which is determined in the process of collecting the lastLogon attribute.
  - CcmExecInstalled - Checks to see if the SMS Agent Host (SCCM) Service is installed.
  - CcmExecStatus - Checks the status of the SMS Agent Host (SCCM) Service.
  - HealthServiceInstalled - Checks to see if the Microsoft Monitoring Agent (SCOM) Service is installed.
  - HealthServiceStatus - Checks the status of the Microsoft Monitoring Agent (SCOM) Service.
  - FRSSaaSClientAgentInstalled - Checks to see if the FrontRange SaaS ClientAgent Service is installed.
  - FRSSaaSClientAgentStatus - Checks the status of the FrontRange SaaS ClientAgent Service.

  Note that the services will depend on the services you list in the
  $Services array.

  You may notice a question mark (?) in various attributes, such as the
  OperatingSystem string. Refer to Microsoft KB829856 for an explanation.

  To provide accurate data we determine a stale computer account using
  three different parameters.
  For Servers:
    1) Password last set more than 90 days ago
    2) Last logged in more than 30 days ago
    3) It's not pingable
  For Workstations:
    1) Password last set more than 90 days ago
    2) Last logged in more than 30 days ago

  Computers change their passwword if and when they feel like it. The
  domain doesn't initiate the change. It is controlled by three values
  under the following registry key:
  - HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\Netlogon\Parameters
    - DisablePasswordChange
    - MaximumPasswordAge
    - RefusePasswordChange
  If these values are not present, the default value of 30 days will be
  used.

  Script Name: Get-AllComputersAccounts.ps1
  Release 1.4
  Modified by [email protected] 27th February 2014
  Written by [email protected] 31st January 2014

  References:
  - http://blogs.technet.com/b/ken_brumfield/archive/2008/09/16/identifying-stale-user-and-computer-accounts.aspx
  - http://blogs.technet.com/b/askds/archive/2011/08/23/cluster-and-stale-computer-accounts.aspx
  - http://blogs.technet.com/b/askds/archive/2009/02/15/test2.aspx
  - http://blogs.metcorpconsulting.com/tech/?p=1369
  - http://blogs.msdn.com/b/clustering/archive/2011/08/17/10197069.aspx
  - http://gallery.technet.microsoft.com/scriptcenter/Get-Active-Directory-User-bbcdd771
  - http://gallery.technet.microsoft.com/scriptcenter/Determine-if-a-computer-is-cdd20473
#>

#-------------------------------------------------------------

# Set this to true to process workstations only. Setting it to
# false will process servers only.
$Workstations = $True

# Set this to true to get an accurate last logon time. Only set
# to true if needed.
$ExactLastLogon = $False

# Set this to true to validate if the server is virtual. This is
# only valid for servers.
$ValidateVirtualStatus = $True

# Set this to true to check for the existence of all services
# in the $Services array. This is only valid for servers.
$CheckForService = $True

# Set this to the names of the services to be checked. This is
# only valid for servers.
$Services = @("CcmExec", "HealthService", "FRSSaaSClientAgent")

# Set this value to true if you want to see the progress bar.
$ProgressBar = $True

#-------------------------------------------------------------

# Import the Active Directory Module
Import-Module ActiveDirectory -WarningAction SilentlyContinue
if ($Error.Count -eq 0) {
    #Write-Host "Successfully loaded Active Directory Powershell's module`n" -ForeGroundColor Green
}
else {
    Write-Host "Error while loading Active Directory Powershell's module : $Error`n" -ForeGroundColor Red
    exit
}

#-------------------------------------------------------------

Function Get-RemoteServerVirtualStatus {
    <#
    .SYNOPSIS
        Validate if a remote server is virtual or physical
    .DESCRIPTION
        Uses wmi (along with an optional credential) to determine if a remote computers, or list of remote computers are virtual.
        If found to be virtual, a best guess effort is done on which type of virtual platform it is running on.
    .PARAMETER ComputerName
        Computer or IP address of machine
    .PARAMETER PromptForCredential
        Set this if you want the function to prompt for alternate credentials.
    .PARAMETER Credential
        Provide an alternate credential
    .EXAMPLE
        $Credential = Get-Credential
        Get-RemoteServerVirtualStatus 'Server1','Server2' -Credential $Credential | select ComputerName,IsVirtual,VirtualType | ft

        Description:
        ------------------
        Using an alternate credential, determine if server1 and server2 are virtual. Return the results along with the type of virtual machine it might be.
    .EXAMPLE
        (Get-RemoteServerVirtualStatus server1).IsVirtual

        Description:
        ------------------
        Determine if server1 is virtual and returns either true or false.

    .LINK
        http://www.the-little-things.net/
    .LINK
        http://nl.linkedin.com/in/zloeber
    .NOTES

        Name       : Get-RemoteServerVirtualStatus
        Version    : 1.0.0 07/27/2013
                     - First release
        Author     : Zachary Loeber
        Disclaimer : This script is provided AS IS without warranty of any kind. I
                     disclaim all implied warranties including, without limitation,
                     any implied warranties of merchantability or of fitness for a
                     particular purpose. The entire risk arising out of the use or
                     performance of the sample scripts and documentation remains
                     with you. In no event shall I be liable for any damages
                     whatsoever (including, without limitation, damages for loss of
                     business profits, business interruption, loss of business
                     information, or other pecuniary loss) arising out of the use of or
                     inability to use the script or documentation.

        Copyright  : I believe in sharing knowledge, so this script and its use is
                     subject to : http://creativecommons.org/licenses/by-sa/3.0/
    #>
    [cmdletBinding(SupportsShouldProcess = $true)]
    param(
        [parameter( Position = 0,
            Mandatory = $true,
            ValueFromPipeline = $true,
            HelpMessage = "Computer or IP address of machine to test")]
        [string[]]$ComputerName,
        [parameter( HelpMessage = "Set this if you want the function to prompt for alternate credentials.")]
        [switch]$PromptForCredential,
        [parameter( HelpMessage = "Pass an alternate credential")]
        [System.Management.Automation.PSCredential]$Credential = $null
    )
    BEGIN {
        if ($PromptForCredential) {
            $Credential = Get-Credential
        }
        $WMISplat = @{}
        if ($null -ne $Credential) {
            $WMISplat.Credential = $Credential
        }
    }
    PROCESS {
        $results = @()
        $computernames = @()
        $computernames += $ComputerName

        foreach ($computer in $computernames) {
            $WMISplat.ComputerName = $computer
            try {
                $wmibios = Get-WmiObject Win32_BIOS @WMISplat -ErrorAction Stop | Select-Object version, serialnumber
                $wmisystem = Get-WmiObject Win32_ComputerSystem @WMISplat -ErrorAction Stop | Select-Object model, manufacturer
                $CanConnect = $true
            }
            catch {
                $CanConnect = $false
            }
            if ($CanConnect) {
                $ResultProps = @{
                    ComputerName = $computer
                    BIOSVersion  = $wmibios.Version
                    SerialNumber = $wmibios.serialnumber
                    Manufacturer = $wmisystem.manufacturer
                    Model        = $wmisystem.model
                    IsVirtual    = $false
                    VirtualType  = $null
                }
                if ($wmibios.Version -match "VIRTUAL") {
                    $ResultProps.IsVirtual = $true
                    $ResultProps.VirtualType = "Hyper-V"
                }
                elseif ($wmibios.Version -match "A M I") {
                    $ResultProps.IsVirtual = $true
                    $ResultProps.VirtualType = "Virtual PC"
                }
                elseif ($wmibios.Version -like "*Xen*") {
                    $ResultProps.IsVirtual = $true
                    $ResultProps.VirtualType = "Xen"
                }
                elseif ($wmibios.SerialNumber -like "*VMware*") {
                    $ResultProps.IsVirtual = $true
                    $ResultProps.VirtualType = "VMWare"
                }
                elseif ($wmisystem.manufacturer -like "*Microsoft*") {
                    $ResultProps.IsVirtual = $true
                    $ResultProps.VirtualType = "Hyper-V"
                }
                elseif ($wmisystem.manufacturer -like "*VMWare*") {
                    $ResultProps.IsVirtual = $true
                    $ResultProps.VirtualType = "VMWare"
                }
                elseif ($wmisystem.model -like "*Virtual*") {
                    $ResultProps.IsVirtual = $true
                    $ResultProps.VirtualType = "Unknown Virtual Machine"
                }
                $results += New-Object PsObject -Property $ResultProps
            }
            else {
                #"Cannot connect via WMI to determine if it's virtual"
            }
        }
    }
    END {
        return $results
    }
}

#-------------------------------------------------------------

# Get the script path
$ScriptPath = { Split-Path $MyInvocation.ScriptName }
If ($Workstations -eq $False) {
    $ReferenceFile = $(&$ScriptPath) + "\AllServerAccountsReport.csv"
}
else {
    $ReferenceFile = $(&$ScriptPath) + "\AllWorkstationAccountsReport.csv"
}

$DNSRoot = (Get-ADDomain).DNSRoot

If ($Workstations -eq $False) {
    # Get All Servers filtering out Cluster Name Objects (CNOs) and Virtual computer Objects (VCOs)
    $Computers = Get-ADComputer -Filter * -Properties Name, Operatingsystem, passwordLastSet, LastLogonDate, servicePrincipalName | Where-Object { ($_.Operatingsystem -like '*server*') -AND !($_.serviceprincipalname -like '*MSClusterVirtualServer*') } | Sort-Object Name
}
Else {
    # Get All Workstations
    $Computers = Get-ADComputer -Filter * -Properties Name, Operatingsystem, passwordLastSet, LastLogonDate | Where-Object { ($_.Operatingsystem -like '*windows*') -AND !($_.Operatingsystem -like '*server*') } | Sort-Object Name
}

# Get Domain Controllers
$DomainControllers = Get-ADDomainController -Filter * | Sort-Object Name

$array = @()
$TotalProcessed = 0
$Count = 0
$Count = $Computers.Count

Write-Host -ForegroundColor Green "There are $Count computer objects to process.`n"

ForEach ($Computer in $Computers) {
    Write-Host -ForegroundColor Green "Processing"($Computer.Name)

    # On the very rare occasion I've found that the DNSHostName attribute may not be set.
    # When that happens we assume that the $ComputerDomain equals the DNS domain name
    # from where the script is being run from.
    If ($NULL -ne $Computer.DNSHostName) {
        $ComputerDomain = $Computer.DNSHostName.Substring(($Computer.DNSHostName.Split(".")[0].length + 1))
    }
    Else {
        $ComputerDomain = $DNSRoot
    }
    Write-Host -ForegroundColor Green "- Domain: $ComputerDomain"
    Write-Host -ForegroundColor Green "- OperatingSystem: "($Computer.OperatingSystem)

    $output = New-Object PSObject
    $output | Add-Member NoteProperty -Name "ComputerDomain" $ComputerDomain
    $output | Add-Member NoteProperty -Name "ComputerName" $Computer.Name
    $output | Add-Member NoteProperty -Name "OperatingSystem" $Computer.OperatingSystem

    If ($Workstations -eq $False) {
        # Check to see if it's physical or virtual.
        If ($ValidateVirtualStatus) {
            $VirtualStatus = Get-RemoteServerVirtualStatus ($Computer.Name)
            $IsVirtual = $VirtualStatus.IsVirtual
            $VirtualType = $VirtualStatus.VirtualType
            If ($IsVirtual -eq $True) {
                Write-Host -ForegroundColor Green "- This is a virtual machine."
                Write-Host -ForegroundColor Green "- Hypervisor: $VirtualType"
            }
            elseIf ($IsVirtual -eq $False) {
                Write-Host -ForegroundColor Green "- This is a physical machine."
                $VirtualType = "N/A"
            }
            else {
                $IsVirtual = "unable to determine"
                $VirtualType = "unable to determine"
                Write-Host -ForegroundColor Red "- $IsVirtual if it's virtual."
            }
            $output | Add-Member NoteProperty -Name "IsVirtual" $IsVirtual
            $output | Add-Member NoteProperty -Name "Hypervisor" $VirtualType
        }
    }

    If ($Computer.Enabled) {
        Write-Host -ForegroundColor Green "- Account Enabled: $($Computer.Enabled)"
    }
    else {
        Write-Host -ForegroundColor Red "- Account Enabled: $($Computer.Enabled)"
    }
    $output | Add-Member NoteProperty -Name "AccountEnabled" $Computer.Enabled

    If ($Workstations -eq $False) {
        $IsPingable = $False
        if (Test-Connection -Cn $Computer.Name -BufferSize 16 -Count 1 -ea 0 -quiet) {
            $IsPingable = $True
            Write-Host -ForegroundColor Green "- Can be pinged: $IsPingable"
        }
        else {
            Write-Host -ForegroundColor Red "- Can be pinged: $IsPingable"
        }
        $output | Add-Member NoteProperty -Name "IsPingable" $IsPingable
    }

    Write-Host -ForegroundColor Green "- Password last set: $($Computer.passwordLastSet)"
    $PasswordTooOld = $False
    If ($Computer.passwordLastSet -le (Get-Date).AddDays(-90)) {
        Write-Host -ForegroundColor Red "  it was set more than 90 days ago."
        $PasswordTooOld = $True
    }
    elseIf ($Computer.passwordLastSet -le (Get-Date).AddDays(-60)) {
        Write-Host -ForegroundColor yellow "  it was set more than 60 days ago."
    }
    elseIf ($Computer.passwordLastSet -le (Get-Date).AddDays(-30)) {
        Write-Host -ForegroundColor yellow "  it was set more than 30 days ago."
    }
    $output | Add-Member NoteProperty -Name "PasswordLastChanged" $Computer.passwordLastSet

    Write-Host -ForegroundColor Green "- Last logon time stamp: $($Computer.LastLogonDate)"
    $HasNotRecentlyLoggedOn = $False
    If ($Computer.LastLogonDate -le (Get-Date).AddDays(-30)) {
        Write-Host -ForegroundColor Red "  it has not logged in for more than 30 days."
        $HasNotRecentlyLoggedOn = $True
    }
    $output | Add-Member NoteProperty -Name "LastLogonDate" $Computer.LastLogonDate

    If ($ExactLastLogon) {
        Write-Host -ForegroundColor Green "- Finding a more accurate last logon time..."
        $RealLastLogonDate = "01/01/1601 08:00:00"
        ForEach ($DomainController in $DomainControllers) {
            if ($DomainController.Domain -eq $ComputerDomain) {
                if (Test-Connection -Cn $DomainController.Name -BufferSize 16 -Count 1 -ea 0 -quiet) {
                    Write-Host -ForegroundColor Green "  - Checking against $($DomainController.Name)"
                    Try {
                        $Lastlogon = Get-ADComputer -Identity $Computer.Name -Properties LastLogon -Server $DomainController.Name
                        if ($RealLastLogonDate -le [DateTime]::FromFileTime($Lastlogon.LastLogon)) {
                            $RealLastLogonDate = [DateTime]::FromFileTime($Lastlogon.LastLogon)
                            $LastusedDC = $DomainController.Name
                        }
                    }
                    Catch {
                        # There was an error connecting to $DomainController.Name
                    }
                }
            }
        }
        if ($RealLastLogonDate -match "1/01/1601") {
            $RealLastLogonDate = "never logged on before"
            $LastusedDC = ""
        }
        If ($RealLastLogonDate -ne "never logged on before") {
            Write-Host -ForegroundColor Green "  - It last logged on to $LastusedDC on $RealLastLogonDate"
        }
        else {
            Write-Host -ForegroundColor Yellow "  - It has $RealLastLogonDate"
        }
        $output | Add-Member NoteProperty -Name "RealLastLogonDate" $RealLastLogonDate
        $output | Add-Member NoteProperty -Name "LastLogonDC" $LastusedDC
    }

    # Check if it's a stale account.
    $IsStale = $False
    If ($Workstations -eq $False) {
        If ($PasswordTooOld -eq $True -AND $HasNotRecentlyLoggedOn -eq $True -AND $IsPingable -eq $False) {
            $IsStale = $True
        }
    }
    else {
        If ($PasswordTooOld -eq $True -AND $HasNotRecentlyLoggedOn -eq $True) {
            $IsStale = $True
        }
    }
    If ($IsStale) {
        Write-Host -ForegroundColor Red "- It's highly probable that this is a stale account."
    }
    $output | Add-Member NoteProperty -Name "StaleAccount" $IsStale

    If ($Workstations -eq $False) {
        If ($CheckForService) {
            # Create new variable for each service in the $Services array
            ForEach ($Service in $Services) {
                $tempvar1 = $service + "Installed"
                new-variable -name $tempvar1 -value $False -Force
                $tempvar2 = $service + "Status"
                new-variable -name $tempvar2 -value "N/A" -Force
                $output | Add-Member NoteProperty -Name $tempvar1 (get-variable -name $tempvar1 -valueonly)
                $output | Add-Member NoteProperty -Name $tempvar2 (get-variable -name $tempvar2 -valueonly)
            }
            Try {
                $serviceObj = Get-Service -computername ($Computer.Name) -include $Services | Select-Object Name, Status
                If ($NULL -ne $serviceObj) {
                    $InstalledServices = @()
                    ForEach ($obj in $serviceObj) {
                        $InstalledServices += $obj.Name
                        If ($obj.Status -eq "Running") {
                            Write-Host -ForegroundColor green "- $($obj.Name) service found in a $($obj.Status) state."
                        }
                        Else {
                            Write-Host -ForegroundColor red "- $($obj.Name) service found in a $($obj.Status) state."
                        }
                        $tempvar1 = $obj.Name + "Installed"
                        $tempvar2 = $obj.Name + "Status"
                        set-variable -name $tempvar1 -value $True
                        set-variable -name $tempvar2 -value $obj.Status
                        $output.PSObject.Properties.Remove($tempvar1)
                        $output | Add-Member NoteProperty -Name $tempvar1 (get-variable -name $tempvar1 -valueonly)
                        $output.PSObject.Properties.Remove($tempvar2)
                        $output | Add-Member NoteProperty -Name $tempvar2 (get-variable -name $tempvar2 -valueonly)
                    }
                    ForEach ($Service in $Services) {
                        If ($InstalledServices -notcontains $Service) {
                            Write-Host -ForegroundColor red "- $Service service not installed."
                        }
                    }
                }
                Else {
                    Write-Host -ForegroundColor red "- None of the services are installed."
                }
            }
            Catch {
                $ComputerError = "$true"
                $ErrorDescription = "Error connecting using the Get-Service cmdlet."
                Write-Host -ForegroundColor Red "- $ErrorDescription"
            }
        }
    }

    $array += $output

    $TotalProcessed ++
    $percent = "{0:P}" -f ($TotalProcessed / $Count)
    Write-Host -ForegroundColor green "Processed $TotalProcessed of $Count computer accounts = $percent complete."
    Write-Host " "
    If ($ProgressBar) {
        Write-Progress -Activity 'Processing Users' -Status ("Username: {0}" -f $($Computer.Name)) -PercentComplete (($TotalProcessed / $Count) * 100)
    }
}

# Write-Output $array | Format-Table
$array | Export-Csv -notype -path "$ReferenceFile"

# Remove the quotes
(Get-Content "$ReferenceFile") | ForEach-Object { $_ -replace '"', "" } | out-file "$ReferenceFile" -Force -Encoding ascii

Back to Top


Download

Please feel free to copy parts of the script or if you would like to download the entire script, simple click the download button. You can download the complete repository in a zip file by clicking the Download link in the menu bar on the left hand side of the page.


Report Issues

You can report an issue or contribute to this site on GitHub. Simply click the button below and add any relevant notes. I will attempt to respond to all issues as soon as possible.

Issue


Back to Top