Sbloccare le informazioni di Active Directory con PowerShell: recuperare i dati dell’ultimo accesso per i computer in AD 

Introduzione

Capire quando i dispositivi hanno effettuato l’ultimo accesso ad Active Directory (AD) è fondamentale per mantenere un ambiente IT sicuro e ben gestito. I sistemi inattivi o raramente utilizzati possono comportare rischi, come punti di accesso non autorizzati o inefficienze nell’allocazione delle risorse. Lo script PowerShell fornito è una solida soluzione per gli amministratori IT e i provider di servizi gestiti (MSP) per recuperare e analizzare in modo efficiente i dati dell’ultimo accesso per tutti i computer nel loro ambiente AD.

Contesto

Active Directory è la spina dorsale per la gestione degli utenti e dei dispositivi in molti ambienti aziendali. Il monitoraggio dei timestamp dell’ultimo accesso ai computer è essenziale per la verifica, la conformità e il mantenimento di una directory ordinata. Questo script semplifica il processo tradizionalmente macchinoso di raccolta di tali dati, sfruttando le capacità di automazione di PowerShell e integrandosi con strumenti come NinjaOne per estendere le funzionalità. Non solo recupera i timestamp dell’ultimo accesso, ma fornisce anche opzioni per effettuare una query su altri dati utente e visualizzare i risultati in un formato personalizzabile.

Lo script per recuperare i dati dell’ultimo accesso:

#Requires -Version 5.1

<#
.SYNOPSIS
    Gets the last login time for all computers in Active Directory.
.DESCRIPTION
    Gets the last login time for all computers in Active Directory.
    The last login time is retrieved from the LastLogonTimeStamp property of the computer object.
    If the user name cannot be retrieved from an offline computer, the script will return Unknown.
    If the computer name cannot be retrieved, the script will return Unknown.

    By using this script, you indicate your acceptance of the following legal terms as well as our Terms of Use at https://www.ninjaone.com/terms-of-use.
    Ownership Rights: NinjaOne owns and will continue to own all right, title, and interest in and to the script (including the copyright). NinjaOne is giving you a limited license to use the script in accordance with these legal terms. 
    Use Limitation: You may only use the script for your legitimate personal or internal business purposes, and you may not share the script with another party. 
    Republication Prohibition: Under no circumstances are you permitted to re-publish the script in any script library or website belonging to or under the control of any other software provider. 
    Warranty Disclaimer: The script is provided “as is” and “as available”, without warranty of any kind. NinjaOne makes no promise or guarantee that the script will be free from defects or that it will meet your specific needs or expectations. 
    Assumption of Risk: Your use of the script is at your own risk. You acknowledge that there are certain inherent risks in using the script, and you understand and assume each of those risks. 
    Waiver and Release: You will not hold NinjaOne responsible for any adverse or unintended consequences resulting from your use of the script, and you waive any legal or equitable rights or remedies you may have against NinjaOne relating to your use of the script. 
    EULA: If you are a NinjaOne customer, your use of the script is subject to the End User License Agreement applicable to you (EULA).

.EXAMPLE
    (No Parameters)
    ## EXAMPLE OUTPUT WITHOUT PARAMS ##

PARAMETER: -WysiwygCustomField "myWysiwygCustomField"
    Saves results to a WYSIWYG Custom Field.
.EXAMPLE
    -WysiwygCustomField "myWysiwygCustomField"
    ## EXAMPLE OUTPUT WITH WysiwygCustomField ##
    [Info] Found 10 computers.
    [Info] Attempting to set Custom Field 'myWysiwygCustomField'.
    [Info] Successfully set Custom Field 'myWysiwygCustomField'!

PARAMETER: -QueryForLastUserLogon "true"
    When checked, the script will query for the last user logon time for each computer.
    Note that this will take longer to run and will try to connect to each computer in the domain.
.EXAMPLE
    -QueryForLastUserLogon "true"
    ## EXAMPLE OUTPUT WITH QueryForLastUserLogon ##
    [Warn] Remote computer WIN-1234567891 is not available.
    [Info] Found 2 computers.

    Computer                  Last Logon Date   Last Login in Days   User
    --------                  ---------------   ------------------   ----
    WIN-1234567891            2024-04-01 12:00   0                   Unknown
    WIN-1234567890            2024-04-01 12:00   0                   Fred
    WIN-9876543210            2023-04-01 12:00   32                  Bob

.NOTES
    Minimum OS Architecture Supported: Windows Server 2016
    Release Notes: Initial Release
#>

[CmdletBinding()]
param (
    [Parameter()]
    [String]$WysiwygCustomField,
    [Parameter()]
    [Switch]$QueryForLastUserLogon
)

begin {
    # CIM timeout
    $CIMTimeout = 10

    function Test-IsElevated {
        $id = [System.Security.Principal.WindowsIdentity]::GetCurrent()
        $p = New-Object System.Security.Principal.WindowsPrincipal($id)
        $p.IsInRole([System.Security.Principal.WindowsBuiltInRole]::Administrator)
    }
    function Set-NinjaProperty {
        [CmdletBinding()]
        Param(
            [Parameter(Mandatory = $True)]
            [String]$Name,
            [Parameter()]
            [String]$Type,
            [Parameter(Mandatory = $True, ValueFromPipeline = $True)]
            $Value,
            [Parameter()]
            [String]$DocumentName
        )
    
        $Characters = $Value | Measure-Object -Character | Select-Object -ExpandProperty Characters
        if ($Characters -ge 10000) {
            throw [System.ArgumentOutOfRangeException]::New("Character limit exceeded, value is greater than 10,000 characters.")
        }
        
        # If we're requested to set the field value for a Ninja document we'll specify it here.
        $DocumentationParams = @{}
        if ($DocumentName) { $DocumentationParams["DocumentName"] = $DocumentName }
        
        # This is a list of valid fields that can be set. If no type is given, it will be assumed that the input doesn't need to be changed.
        $ValidFields = "Attachment", "Checkbox", "Date", "Date or Date Time", "Decimal", "Dropdown", "Email", "Integer", "IP Address", "MultiLine", "MultiSelect", "Phone", "Secure", "Text", "Time", "URL", "WYSIWYG"
        if ($Type -and $ValidFields -notcontains $Type) { Write-Warning "$Type is an invalid type! Please check here for valid types. https://ninjarmm.zendesk.com/hc/en-us/articles/16973443979789-Command-Line-Interface-CLI-Supported-Fields-and-Functionality" }
        
        # The field below requires additional information to be set
        $NeedsOptions = "Dropdown"
        if ($DocumentName) {
            if ($NeedsOptions -contains $Type) {
                # We'll redirect the error output to the success stream to make it easier to error out if nothing was found or something else went wrong.
                $NinjaPropertyOptions = Ninja-Property-Docs-Options -AttributeName $Name @DocumentationParams 2>&1
            }
        }
        else {
            if ($NeedsOptions -contains $Type) {
                $NinjaPropertyOptions = Ninja-Property-Options -Name $Name 2>&1
            }
        }
        
        # If an error is received it will have an exception property, the function will exit with that error information.
        if ($NinjaPropertyOptions.Exception) { throw $NinjaPropertyOptions }
        
        # The below types require values not typically given in order to be set. The below code will convert whatever we're given into a format ninjarmm-cli supports.
        switch ($Type) {
            "Checkbox" {
                # While it's highly likely we were given a value like "True" or a boolean datatype it's better to be safe than sorry.
                $NinjaValue = [System.Convert]::ToBoolean($Value)
            }
            "Date or Date Time" {
                # Ninjarmm-cli expects the GUID of the option to be selected. Therefore, the given value will be matched with a GUID.
                $Date = (Get-Date $Value).ToUniversalTime()
                $TimeSpan = New-TimeSpan (Get-Date "1970-01-01 00:00:00") $Date
                $NinjaValue = $TimeSpan.TotalSeconds
            }
            "Dropdown" {
                # Ninjarmm-cli is expecting the guid of the option we're trying to select. So we'll match up the value we were given with a guid.
                $Options = $NinjaPropertyOptions -replace '=', ',' | ConvertFrom-Csv -Header "GUID", "Name"
                $Selection = $Options | Where-Object { $_.Name -eq $Value } | Select-Object -ExpandProperty GUID
        
                if (-not $Selection) {
                    throw [System.ArgumentOutOfRangeException]::New("Value is not present in dropdown")
                }
        
                $NinjaValue = $Selection
            }
            default {
                # All the other types shouldn't require additional work on the input.
                $NinjaValue = $Value
            }
        }
        
        # We'll need to set the field differently depending on if its a field in a Ninja Document or not.
        if ($DocumentName) {
            $CustomField = Ninja-Property-Docs-Set -AttributeName $Name -AttributeValue $NinjaValue @DocumentationParams 2>&1
        }
        else {
            $CustomField = Ninja-Property-Set -Name $Name -Value $NinjaValue 2>&1
        }
        
        if ($CustomField.Exception) {
            throw $CustomField
        }
    }
}
process {
    if (-not (Test-IsElevated)) {
        Write-Host -Object "[Error] Access Denied. Please run with Administrator privileges."
        exit 1
    }

    # Get Script Variables and override parameters with them
    if ($env:wysiwygCustomField -and $env:wysiwygCustomField -notlike "null") {
        $WysiwygCustomField = $env:wysiwygCustomField
    }
    if ($env:queryForLastUserLogon -and $env:queryForLastUserLogon -notlike "null") {
        if ($env:queryForLastUserLogon -eq "true") {
            $QueryForLastUserLogon = $true
        }
        else {
            $QueryForLastUserLogon = $false
        }
    }

    # Check that Active Directory module is available
    if (-not (Get-Module -ListAvailable -Name ActiveDirectory)) {
        Write-Host "[Error] Active Directory module is not available. Please install it and try again."
        exit 1
    }

    # Get the computer system from the CIM
    $ComputerSystem = $(Get-CimInstance -ClassName Win32_ComputerSystem)

    # Check if this script is running on a domain joined computer
    if ($ComputerSystem.PartOfDomain -eq $false) {
        Write-Host "[Error] This script must be run on a domain joined computer."
        exit 1
    }

    # Check if this script is running on a domain controller
    switch ($ComputerSystem.DomainRole) {
        0 { Write-Host "[Info] Running script on a Standalone Workstation." }
        1 { Write-Host "[Info] Running script on a Member Workstation." }
        2 { Write-Host "[Info] Running script on a Standalone Server." }
        3 { Write-Host "[Info] Running script on a Member Server." }
        4 { Write-Host "[Info] Running script on a Backup Domain Controller." }
        5 { Write-Host "[Info] Running script on a Primary Domain Controller." }
    }

    # Get the SearchBase for the domain
    $Domain = "DC=$($ComputerSystem.Domain -split "\." -join ",DC=")"

    # Get Computers from Active Directory
    try {
        $Computers = Get-ADComputer -Filter { (Enabled -eq $true) } -Properties Name, LastLogonTimeStamp -SearchBase "$Domain" -ErrorAction Stop
    }
    catch {
        Write-Host "[Error] Failed to get computers. Make sure this is running on a domain controller."
        exit 1
    }

    $IsFirstError = $true

    $LastLogonInfo = foreach ($Computer in $Computers) {
        try {
            # Get the LastLogonTimeStamp for the computer from Active Directory
            $PCInfo = Get-ADComputer -Identity $Computer.Name -Properties LastLogonTimeStamp -ErrorAction Stop | Select-Object -Property @(
                @{Name = "Computer"; Expression = { $_.Name } },
                @{Name = "LastLogon"; Expression = { [DateTime]::FromFileTime($_.LastLogonTimeStamp) } }
            )
        }
        catch {
            # This should only happen if the script is not running as the system user on a domain controller or not as a domain admin
            Write-Debug "[Debug] $($_.Exception.Message)"
            Write-Host "[Warn] Failed to get details for $($Computer.Name) from Active Directory. Skipping."
            continue
        }
        try {
            if ($QueryForLastUserLogon) {
                # Get the User Principal Name from the computer
                $LastUserLogonInfo = Get-CimInstance -ClassName Win32_UserProfile -ComputerName $Computer.name -OperationTimeoutSec $CIMTimeout -ErrorAction Stop | Where-Object { $_.LocalPath -like "*Users*" } | Sort-Object -Property LastUseTime | Select-Object -Last 1
                $SecIdentifier = New-Object System.Security.Principal.SecurityIdentifier($LastUserLogonInfo.SID) -ErrorAction Stop
                $UserName = $SecIdentifier.Translate([System.Security.Principal.NTAccount])
            }
        }
        catch {
            if ($null -eq $UserName) {
                if ($IsFirstError) {
                    # Only show on the first error
                    Write-Debug "[Debug] $($_.Exception.Message)"
                    Write-Host "[Error] Failed to connect to 1 or more computers via Get-CimInstance."
                    $IsFirstError = $false
                }
                Write-Host "[Warn] Remote computer $($Computer.Name) is not available or could not be queried."
            }
        }

        if ($null -eq $UserName) {
            $UserName = [PSCustomObject]@{
                value = "Unknown"
            }
        }
        if ($null -eq $PCInfo.LastLogon) {
            $PCInfo = [PSCustomObject]@{
                Computer  = $Computer.Name
                LastLogon = "Unknown"
            }
            Write-Host "[Warn] Failed to get LastLogonTimeStamp for $($Computer.Name)."
        }

        # Get the number of days since the last login
        $LastLoginDays = try {
            0 - $(Get-Date -Date $PCInfo.LastLogon).Subtract($(Get-Date)).Days
        }
        catch {
            # Return unknown if the date is invalid or does not exist
            "Unknown"
        }

        # Output the results
        if ($QueryForLastUserLogon) {
            [PSCustomObject]@{
                'Computer'           = $PCInfo.Computer
                'Last Logon Date'    = $PCInfo.LastLogon
                'Last Login in Days' = $LastLoginDays
                'User'               = $UserName.value
            }
        }
        else {
            [PSCustomObject]@{
                'Computer'           = $PCInfo.Computer
                'Last Logon Date'    = $PCInfo.LastLogon
                'Last Login in Days' = $LastLoginDays
            }
        }

        $PCInfo = $null
        $LastUserLogonInfo = $null
        $SecIdentifier = $null
        $UserName = $null
    }

    # Output the number of computers found
    if ($LastLogonInfo -and $LastLogonInfo.Count -gt 0) {
        Write-Host "[Info] Found $($LastLogonInfo.Count) computers."
    }
    else {
        Write-Host "[Error] No computers were found."
        $ExitCode = 1
    }

    function Write-LastLoginInfo {
        param ()
        $LastLogonInfo | Format-Table -AutoSize | Out-String -Width 4000 | Write-Host
    }

    # Save the results to a custom field
    if ($WysiwygCustomField) {
        $LastLoginOkayDays = 30
        $LastLoginTooOldDays = 90
        # Convert the array to an HTML table
        $HtmlTable = $LastLogonInfo | ConvertTo-Html -Fragment
        # Set the color of the rows based on the last logon time
        $HtmlTable = $HtmlTable -split [Environment]::NewLine | ForEach-Object {
            if ($_ -match "<td>(?'LastLoginDays'\d+)<\/td>") {
                # Get the last login days from the HTML table
                [int]$LastLoginDays = $Matches.LastLoginDays
                if ($LastLoginDays -lt $LastLoginTooOldDays -and $LastLoginDays -ge $LastLoginOkayDays) {
                    # warning = 31 days to 89 days
                    $_ -replace "<tr><td>", '<tr class="warning"><td>'
                }
                elseif ($LastLoginDays -ge $LastLoginTooOldDays) {
                    # danger = 90 days or more
                    $_ -replace "<tr><td>", '<tr class="danger"><td>'
                }
                else {
                    # success = 30 days or less
                    $_ -replace "<tr><td>", '<tr class="success"><td>'
                }
            }
            else {
                $_
            }
        }
        # Set the width of the table to 10% to reduce the width of the table to its minimum possible width
        $HtmlTable = $HtmlTable -replace "<table>", "<table style='white-space:nowrap;'>"
        try {
            Write-Host "[Info] Attempting to set Custom Field '$WysiwygCustomField'."
            Set-NinjaProperty -Name $WysiwygCustomField -Value $($HtmlTable | Out-String)
            Write-Host "[Info] Successfully set Custom Field '$WysiwygCustomField'!"
        }
        catch {
            Write-Host "[Error] Failed to set Custom Field '$WysiwygCustomField'."
            Write-LastLoginInfo
            $ExitCode = 1
        }
    }
    else {
        Write-LastLoginInfo
    }

    exit $ExitCode
}
end {
    
    
    
}

 

Risparmia tempo con gli oltre 300 script del Dojo NinjaOne.

Accedi oggi stesso.

Analisi dettagliata

Lo script per recuperare i dati dell’ultimo accesso è stato progettato per essere potente e versatile, in modo che i professionisti IT possano adattarlo alle loro esigenze specifiche. Di seguito è riportata una descrizione passo per passo delle sue funzionalità:

1. Controllo dei prerequisiti:
Lo script per recuperare i dati dell’ultimo accesso verifica innanzitutto l’ambiente del sistema:

a. Conferma di essere in esecuzione su un computer collegato al dominio.

b. Si assicura che il modulo PowerShell di Active Directory sia installato.

2. Gestione dei parametri:
Lo script per recuperare i dati dell’ultimo accesso accetta due parametri chiave:

a. -WysiwygCustomField: Specifica un campo personalizzato in NinjaOne per salvare i risultati.

b. -QueryForLastUserLogon: Consente di effettuare una query sui dettagli dell’ultimo accesso dell’utente da ogni computer, anche se questo allunga i tempi di esecuzione.

3. Query di Active Directory:
Lo script utilizza Get-ADComputer per recuperare gli oggetti computer abilitati insieme al loro LastLogonTimeStamp. Organizza i dati in una tabella leggibile, eventualmente completata con i dettagli dell’ultimo accesso dell’utente.

4. Gestione degli errori e registrazione:
La gestione completa degli errori garantisce l’esecuzione affidabile dello script. Per esempio:

a. I computer offline vengono segnalati con avvisi.

b. Gli errori nella query dei singoli sistemi vengono registrati, ma lo script continua a elaborare gli altri sistemi.

5. Elaborazione e visualizzazione dei dati:

a. Lo script per recuperare i dati dell’ultimo accesso calcola il numero di giorni trascorsi dall’ultimo accesso.

b. Se specificato, i risultati vengono formattati in una tabella HTML per facilitare i report, con righe colorate che indicano la data dell’attività di accesso.

6. Integrazione con NinjaOne:
I risultati possono essere salvati in un campo personalizzato di NinjaOne, rendendoli accessibili per ulteriori analisi e report.

Casi d’uso potenziali

Caso di studio:

L’amministratore IT di un’azienda di medie dimensioni nota un picco di computer inattivi durante una verifica annuale. Utilizzo dello script:

  • Recupera quindi i dati dell’ultimo accesso per tutti i dispositivi del dominio.
  • I sistemi inattivi che non si connettono da oltre 90 giorni vengono segnalati e disattivati.
  • Le informazioni sull’attività degli utenti vengono utilizzate per ottimizzare l’allocazione delle risorse hardware.

Questo approccio migliora la posizione di sicurezza dell’organizzazione riducendo le superfici di attacco e consente di risparmiare sui costi identificando le risorse sottoutilizzate.

Confronti

Tradizionalmente, gli amministratori IT si affidavano a strumenti come la GUI di Active Directory Users and Computers (ADUC) o a software di auditing di terze parti per raccogliere informazioni sugli accessi. Questi metodi spesso non sono scalabili o richiedono un notevole impegno manuale. Al contrario, questo script PowerShell per recuperare i dati dell’ultimo accesso:

  • Automatizza il processo di recupero dei dati, facendo risparmiare tempo.
  • Offre flessibilità grazie a parametri personalizzabili.
  • Si integra con strumenti come NinjaOne per una migliore reportistica.

Domande frequenti

D: Lo script per recuperare i dati dell’ultimo accesso richiede privilegi amministrativi?

Sì, deve essere eseguito con permessi elevati per effettuare efficacemente query dei dati AD.

D: Quanto è precisa la proprietà LastLogonTimeStamp?

Questa proprietà potrebbe non riflettere l’attività in tempo reale, in quanto viene aggiornata di default ogni 14 giorni circa. Per ottenere dati di accesso precisi, si consiglia di interrogare i singoli controller di dominio.

D: Cosa succede se un computer è offline?

I sistemi offline vengono segnalati con avvisi e i loro dettagli utente vengono impostati su “Sconosciuto”.

D: Lo script per recuperare i dati dell’ultimo accesso può essere personalizzato?

Sì, è stato progettato per essere flessibile. Parametri come WysiwygCustomField e QueryForLastUserLogon consentono casi d’uso personalizzati.

Implicazioni

I risultati generati da questo script per recuperare i dati dell’ultimo accesso hanno implicazioni significative per la sicurezza informatica e l’efficienza operativa:

  • Sicurezza migliorata: L’identificazione dei dispositivi inattivi riduce i potenziali vettori di attacco.
  • Miglioramento della conformità: Le organizzazioni possono soddisfare i requisiti di audit mantenendo registrazioni accurate dei dispositivi.
  • Operazioni semplificate: L’automazione elimina le spese manuali di monitoraggio dell’attività del dispositivo.

Raccomandazioni

  1. Esegui lo script per recuperare i dati dell’ultimo accesso regolarmente: Programmare l’esecuzione dello script per recuperare i dati dell’ultimo accesso mensilmente per mantenere un inventario aggiornato dei sistemi attivi.
  2. Usalo in combinazione con policy: Utilizza i risultati per applicare criteri come la disattivazione dei dispositivi inattivi dopo un determinato periodo.
  3. Utilizza credenziali sicure: Assicurati che lo script per recuperare i dati dell’ultimo accesso venga eseguito in un ambiente sicuro per proteggere i dati sensibili di AD.

Considerazioni finali

Per gli amministratori IT e gli MSP, l’automazione del recupero dei dati dell’ultimo accesso è essenziale per mantenere un ambiente AD sicuro ed efficiente. Questo script PowerShell non solo semplifica il processo per recuperare i dati dell’ultimo accesso, ma si integra perfettamente con piattaforme come NinjaOne, consentendo una reportistica e una gestione avanzate. Sfruttando strumenti come questo, le organizzazioni possono ottimizzare le operazioni IT e rafforzare le strutture di sicurezza.

Passi successivi

La creazione di un team IT efficiente ed efficace richiede una soluzione centralizzata che funga da principale strumento per la fornitura di servizi. NinjaOne consente ai team IT di monitorare, gestire, proteggere e supportare tutti i dispositivi, ovunque essi si trovino, senza la necessità di una complessa infrastruttura locale.

Per saperne di più sulla distribuzione remota di script con NinjaOne, fai un tour dal vivo, o inizia la tua prova gratuita della piattaforma NinjaOne.

Categorie:

Ti potrebbe interessare anche

×

Guarda NinjaOne in azione!

Inviando questo modulo, accetto La politica sulla privacy di NinjaOne.

Termini e condizioni NinjaOne

Cliccando sul pulsante “Accetto” qui sotto, dichiari di accettare i seguenti termini legali e le nostre condizioni d’uso:

  • Diritti di proprietà: NinjaOne possiede e continuerà a possedere tutti i diritti, i titoli e gli interessi relativi allo script (compreso il copyright). NinjaOne ti concede una licenza limitata per l’utilizzo dello script in conformità con i presenti termini legali.
  • Limitazione d’uso: Puoi utilizzare lo script solo per legittimi scopi personali o aziendali interni e non puoi condividere lo script con altri soggetti.
  • Divieto di ripubblicazione: In nessun caso ti è consentito ripubblicare lo script in una libreria di script appartenente o sotto il controllo di un altro fornitore di software.
  • Esclusione di garanzia: Lo script viene fornito “così com’è” e “come disponibile”, senza garanzie di alcun tipo. NinjaOne non promette né garantisce che lo script sia privo di difetti o che soddisfi le tue esigenze o aspettative specifiche.
  • Assunzione del rischio: L’uso che farai dello script è da intendersi a tuo rischio. Riconosci che l’utilizzo dello script comporta alcuni rischi intrinseci, che comprendi e sei pronto ad assumerti.
  • Rinuncia e liberatoria: Non riterrai NinjaOne responsabile di eventuali conseguenze negative o indesiderate derivanti dall’uso dello script e rinuncerai a qualsiasi diritto legale o di equità e a qualsiasi rivalsa nei confronti di NinjaOne in relazione all’uso dello script.
  • EULA: Se sei un cliente NinjaOne, l’uso dello script è soggetto al Contratto di licenza con l’utente finale (EULA) applicabile.