Payton Flint's Tech Blog
Menu
  • Home
  • Blog
  • Categories
  • Resources
  • About
  • Contact
Menu

PowerShell – Programmatically Determine the Primary User of a Device

Posted on August 14, 2023August 21, 2023 by paytonflint

Configuration Manager includes the device property and concept of UserDeviceAffinity, with which the Primary User of a device can easily be determined and referenced. But, what if your environment does not have MECM in place? MECM is a lot of overhead for a small environment. How might one maintain this capability?

Well, user log on events are in the Event Logs, so if we can parse those, we can make this determination. I have created a script that looks for corresponding logon/logoff events, determines the difference between them to find the duration, and determines which user has logged the greatest duration logged in to the device. Then, it queries AD to get the user’s actual name. It returns an object with both values stored in properties.

My script will only work for domain-joined computers, and is currently set up to use both remote sessions and normal interactive user logon types.

Here it is on my GitHub:

https://github.com/p8nflnt/Get-WinHostInfo-PsExec/blob/main/Get-UserDeviceAffinity.ps1

<#
.SYNOPSIS
    Get Primary User information for a domain-joined computer
    Not to be confused w/ ConfigMan's Get-CMUserDeviceAffinity

.NOTES
    Name: Get-UserDeviceAffinity
    Author: Payton Flint
    Version: 1.0
    DateCreated: 2023-Aug

.LINK
    https://paytonflint.com/powershell-programmatically-determine-the-primary-user-of-a-device/
    https://github.com/p8nflnt/SysAdmin-Toolbox/blob/main/Get-UserDeviceAffinity.ps1
#>

Function Get-UserDeviceAffinity {
    # Get computer name
    $ComputerName = $env:COMPUTERNAME

    If ($ComputerName -ne $null) {
        # Get current domain name
        $Domain = [System.DirectoryServices.ActiveDirectory.Domain]::GetCurrentDomain()
        $Domain = $Domain.Name
        # Drop domain suffix & convert to uppercase
        $Domain = (($Domain.Split('.')[0]).ToUpper())

        # Get Events w/ ID 4624 (Logon) that match Logon Types: 2
        $LogonType2 = Get-EventLog -LogName Security -ComputerName $ComputerName -ErrorAction SilentlyContinue -InstanceId 4624 -Message `
        "*`r`n	Logon Type:		2`r`n	Restricted*	Account Domain:		$Domain`r`n	Logon*	Logon GUID:		{00000000-0000-0000-0000-000000000000}`r`n*" # Message filter

        # Get Events w/ ID 4624 (Logon) that match Logon Type: 10
        $LogonType10 = Get-EventLog -LogName Security -ComputerName $ComputerName -ErrorAction SilentlyContinue -InstanceId 4624 -Message `
        "*`r`n	Logon Type:		10`r`n	Restricted*	Account Domain:		$Domain`r`n	Logon*	Logon GUID:		{00000000-0000-0000-0000-000000000000}`r`n*" # Message filter

        # Combine events w/ Types 2 & 10
        $LogonEvents = $LogonType2 + $LogonType10

        # Continue only if events matching specified profile are present
        If ($LogonEvents -ne $null) {
            # Create UserSessions array
            $UserSessions = @()

            $LogonEvents | ForEach-Object {
                # Use regex to extract AccountName from message
                $pattern = "(?<=New Logon:\s*\r?\n[\s\S]*Account Name:\s+)(\S+)"
                If ($_.Message -match $pattern) {
                    $AccountName = $matches[1]
                }
                # Use regex to extract LogonID from message
                $pattern = "(?<=New Logon:\s*\r?\n[\s\S]*Logon ID:\s+)(\S+)"
                If ($_.Message -match $pattern) {
                    $LogonID = $matches[1]
                }
                # Get computer name
                $ComputerName = ($_.MachineName.Split('.')[0])

                # Get session end
                $SessionEnd = Get-EventLog -LogName Security -ComputerName $ComputerName -ErrorAction SilentlyContinue -InstanceId 4634,4647 -Message `
                "*`r`n	Logon ID:		$LogonID`r`n*" # Message filter

                # Derive session duration
                If ($SessionEnd -ne $null) {
                    $SessionDuration = $SessionEnd.TimeGenerated - $_.TimeGenerated
                } Else {
                    $SessionDuration = 0
                }
                # Create new session object & add properties
                $SessionObject = New-Object PSObject -Property @{
                    LogonTime =           $_.TimeGenerated
                    AccountName =         $AccountName
                    LogonID =             $LogonID
                    SessionDuration =     $SessionDuration
                    ComputerName =        $ComputerName
                    LogoffTime =          $SessionEnd.TimeGenerated
                }
                # Add session objects to array
                $UserSessions += $SessionObject
            }
            # Group user sessions by account name
            $AcctSessions = $UserSessions | Group-Object -Property AccountName

            # Group user sessions by account name
            $AcctSessions | ForEach-Object {
                $_.Group | ForEach-Object {
                    $TotalDuration += $_.SessionDuration
                }
                # Add total duration of user sessions to each account name group
                $_ | Add-Member -NotePropertyName 'TotalDuration' -NotePropertyValue $TotalDuration
                $TotalDuration = $null
            }
            # Determine the value of the greatest total session duration per user account
            $GreatestDuration = ($AcctSessions | Measure-Object -Property TotalDuration -Maximum).Maximum

            # Filter the array to get the object(s) with the highest weight
            $PrimaryUser = $AcctSessions | Where-Object { $_.TotalDuration -eq $GreatestDuration }
            # Get username
            $UserName = $PrimaryUser.Name
            # Get AD User object
            $ADUser =   Get-AdUser -Filter {SamAccountName -eq $UserName} -ErrorAction SilentlyContinue
            $ADName =   $ADUser | Select-Object -ExpandProperty Name

            # Format output
            Write-Output "Username	: $UserName`r`nName	: $ADName"
        }
    }
} # End Function Get-UserDeviceAffinity

# Execute function
$PrimaryUser = Get-UserDeviceAffinity -ComputerName "$env:COMPUTERNAME"
# Return result
$PrimaryUser

Leave a Reply Cancel reply

Your email address will not be published. Required fields are marked *

About The Author

Author's Portrait

In my journey as a technologist and 11 years of experience as an IT professional, I have found my niche as Director of Infrastructure Services; developing my skillsets in management, scripting, cloud infrastructure, identity management, and networking.

I have experience as a Systems Administrator and Engineer for large enterprises including the DoD, government agencies, and a nuclear-generation site.

I've been blessed to collaborate with engineers at esteemed Fortune 50 corporations, and one of Africa's largest, to ensure successful implementation of my work.

GitHub Button

Credentials

M365 Endpoint Administrator Associate
M365 Fundamentals
Microsoft AZ-900
CompTIA CSIS
CompTIA CIOS
CompTIA Security+
CompTIA Network+
CompTIA A+
  • April 2025
  • December 2024
  • November 2024
  • October 2024
  • September 2024
  • August 2024
  • May 2024
  • April 2024
  • March 2024
  • February 2024
  • January 2024
  • December 2023
  • November 2023
  • October 2023
  • September 2023
  • August 2023
  • July 2023
  • June 2023
  • May 2023
  • April 2023
  • March 2023
  • February 2023
  • January 2023
  • December 2022
  • November 2022
  • October 2022
  • September 2022
  • August 2022
© 2022 Payton Flint | The views and opinions expressed on this website belong solely to the author/owner and do not represent the perspectives of any individuals, institutions, or organizations, whether affiliated personally or professionally, unless explicitly stated otherwise. The content and products on this website are provided as-is with no warranties or guaranties, are for informational/demonstrative purposes only, do not constitute professional advice, and are not to be used maliciously. The author/owner is not responsible for any consequences arising from actions taken based on information provided on this website, nor from the use/misuse of products from this site. All trademarks are the property of their respective owners.