Friday, April 11, 2014

How to use PowerShell to find what computer a user is CURRENTLY logged into.

I recently had an issue where I needed the ability to identify the PC(s) where a user was logged into.  I found part of the answer here: 

http://deployhappiness.com/find-out-what-computer-a-user-logged-into/#comment-5068

I won't reprint it here but the jist is by using a logon/logoff VB script, and a GPO, you can populate a field in the Active Directory record of each PC in a domain.

That part works really well.  After that you would need to search AD for the username to identify the PC.

What I've done is created a PowerShell script with a GUI to do it for you.  Now, it's not finished yet, but it does work.  The code below is dirty and as soon as I get it the way I want I'll update this posting, but for now, it works pretty well.  Don't complain about bugs,I know it has them, I'm working on it.  And, yes, I have no doubt some things could be done better or more efficiently, feel free to post improvements.

What it has (or will have):
  • Self relocates to c:/scripts and drops an icon on the current users desktop for future launches
  • Self relocation is optional
  • Colorized output
  • GUI driven for simplicity
  • Various levels of debugging options
  • Reports items located in both AD "Description" field, and "Managed By" field.

Watch for word wrapping...

<#======================================================================================
         File Name : Find-User.ps1
   Original Author : Kenneth C. Mazie (kcmjr AT kcmjr.com)
                   :
       Description : Will locate the PC a user is currently logged on to.
                   :
             Notes : Normal operation is with no command line options. Requires PowerShell AD module.
                   :
          Warnings :
                   :
             Legal : Public Domain. Modify and redistribute freely. No rights reserved.
                   : SCRIPT PROVIDED "AS IS" WITHOUT WARRANTIES OR GUARANTEES OF
                   : ANY KIND. USE AT YOUR OWN RISK. NO TECHNICAL SUPPORT PROVIDED.
                   :
           Credits : Code snippets and/or ideas came from many sources including but
                   :   not limited to the following:
                   : http://deployhappiness.com/find-out-what-computer-a-user-logged-into/#comment-5068
                   : https://stackoverflow.com/questions/2688547/muliple-foreground-colors-in-powershell-in-one-command
                   :
    Last Update by : Kenneth C. Mazie
   Version History : v1.0 - 04-11-14 - Original
    Change History : v1.1 - 00-00-00 -  
                   :
=======================================================================================#>

clear-host

#-------------------------------[ Begin ]---------------------------------------#>

$Debug1 = $false                                                                    #--[ Set to true to have extra messages to console during run  
$Debug2 = $false                                                                    #--[ Set to true to have extra messages to console during run  
$CurrentVersion = "1.0"
if (!(Get-Module -Name ActiveDirectory)){Import-Module ActiveDirectory}
$ErrorActionPreference = "SilentlyContinue"
$TargetPath = "C:\Scripts"                                                             #--[ Where the script "should" live
$ScriptFullName = ($MyInvocation.MyCommand).Name                                       #--[ EX: script.ps1
$ScriptFullPath = ($MyInvocation.MyCommand).Path                                       #--[ EX: C:\Scripts\script.ps1
$ScriptHomeDir = split-path -parent $ScriptFullPath                                    #--[ EX: C:\Scripts, where the script resides
$ScriptWorkingDir = $pwd.path                                                          #--[ EX: C:\Scripts or C:\temp, where the script executes from
$ScriptShortName = [system.io.path]::GetFilenameWithoutExtension($ScriptFullPath)      #--[ EX: script (no extention)
$osv = [environment]::osversion.VersionString                                          #--[ Get Windows version, not required, just for convenience
$windir = [System.Environment]::ExpandEnvironmentVariables("%WINDIR%")                 #--[ Get %windir% environment variable
#$windir = $env:windir                                                                #--[ Alternate format
$Home = [System.Environment]::ExpandEnvironmentVariables("%USERPROFILE%")           #--[ Get %userprofile% environment variable
$ThisComputer1 = [System.Net.Dns]::GetHostName()                                      #--[ NOTE: No reason for both forms, just because.
$ThisComputer2 = $env:COMPUTERNAME
$Relocate = $false                                                                  #--[ Change to $true to enable automatic relocation of the script

Function LocateUser {
  Param ([string]$TargetUser)
  $TargetUserSAM = $Null
  $TargetUserName = $Null
  $ResultA = @()
  $ResultB = @()
  $FullArray = @()
  $SubArray = @()
  $Computers = Get-ADComputer -Filter * -Properties ManagedBy,Description | Select Name, ManagedBy, Description | Sort -Property Name

  If ($TargetUser.contains(" ")){                                                   #--[ Get name or ID depending on what uer entered.
    [string]$TargetUserSAM = (Get-ADUser -Filter "Name -eq '$TargetUser'" | Select-Object name, samaccountname).samaccountname
    [string]$TargetUserName = $TargetUser.ToUpper()
  }Else{
    [string]$TargetUserName = (Get-ADUser -Filter "samAccountName -eq '$TargetUser'" | Select-Object name, samaccountname).name
    [string]$TargetUserSAM = $TargetUser.ToUpper()
  }
  If ($TargetUserName -eq ""){$TargetUserName = "UNKNOWN"}
  $TargetUser = (Get-Culture).TextInfo.ToTitleCase($TargetUser.ToLower())
  If ($Debug2){Write-Host "Checkpoint 1:  TUname= " $TargetUserName  " TUsam=" $TargetUserSAM " TU=" $TargetUser}

  ForEach ($Computer in $Computers){  #--[ Sort through AD Computers, extract ManagedBy and Description ]--
    $ManagedBy = ""
    $ManagedBy = (((($Computer.managedby).split(",")).split("="))[1]).ToUpper()
    $Description = ""
    $Description = ($Computer.description).ToUpper()
    #If ($Debug2){Write-Host "Checkpoint 2: Comp=" $Computer.name   " ManagedBy=" $ManagedBy   " Description="$Description }  #--[ computer, description, & who is its manager in AD
    If ($Debug2){color-Write Checkpoint 2: -Red Comp = $Computer.name -Yellow LoggedOn = $ManagedBy -Green Description = $Description }  #--[ computer, description, & who is its manager in AD
    $SubArray = $Computer.name,$ManagedBy,$Description
    $FullArray += ,$SubArray    #--[ Fullarray will contain Computer,ManagedBy,Description ]--
  }

  $count = $FullArray.count


 # write-host "B = " $ResultA.count
 # write-host "A = " $ResultB.count

   $ResultA += $TargetUserName + "  (" + $TargetUserSAM + ")`n`nLogged on to:`n"
  $ResultB += "Assigned to:  `n" #$TargetUserName + "  (" + $TargetUserSAM + ")`n"

  While ($count -gt 0){
    #If ($Debug2){Write-Host "Checkpoint 3:  $count   DetectedUser=" $FullArray[$count][1] " TargetUser="  $TargetUserName  "PC-Descr="  $Description}
    If ($Debug2){$FullArrayCount1 = $FullArray[$count][1];color-Write Checkpoint 3: -Magenta $count -Yellow DetectedUser = $FullArrayCount1 -Cyan TargetUser = $TargetUserName -Green PC-Descr = $Description}
  
    If ($FullArray[$count][1] -eq $TargetUserName){
     # If ($Debug2){Write-Host "Checkpoint 4:  FAcount$count-0=" $FullArray[$count][0] "  FAcount$count-1="  $FullArray[$count][1] " User=" $TargetUser }
      $ResultA += $FullArray[$count][0] + "`n"
    }
      
    If ($FullArray[$count][2] -eq $TargetUserName){
      $ResultB += $FullArray[$count][0] + "`n"
      }#write-host $Description}
  
    #If ($Debug2){$FullArrayCount1 = $FullArray[$count][1];$X = $count-1;Write-Host "Checkpoint 5:  FullArrayCount $X = " $FullArrayCount1}
    $count --
  }


  If ($Debug2){$x = $ResultA.count;color-Write Checkpoint 6: -Red ResultACount = $x (includes target)}
    If ($Debug2){color-Write -Red ResultBCount = $ResultB.count (includes target)}

  #write-host "A = " $ResultA.count
  #write-host "B = " $ResultB.count
  #write-host $ResultA[0]  $ResultA[1]
  #write-host $ResultB[0]  $ResultB[1]




  If ($ResultA.count -eq 2){  #-[  If results plus target > 2 then display multiples ]--
    $objLabel.Text = "Results for:   $ResultA[0]"    #--[ Overwrite the text with the username ]--
    $objLabel.TextAlign = [System.Drawing.ContentAlignment]::MiddleCenter
    $objLabel.Refresh()
  
    $objForm.Controls.remove($objTextBox)
  
    #--[ Add Result 1 Lable ]--
    If ($Debug1){Write-Host "Adding result label 1"}
    $objResult1 = New-Object System.Windows.Forms.Label
    $objResult1.Location = New-Object System.Drawing.Size(10,50)
    $objResult1.Size = New-Object System.Drawing.Size(315,20)
    $objResult1.TextAlign = [System.Drawing.ContentAlignment]::MiddleCenter
    $objResult1.Text = "Currently logged onto:   " + $ResultA[1]
    $objForm.Controls.Add($objResult1)

    #--[ Add Result 2 Label ]--
    If ($ResultB[1] -eq $Null){$Assigned = "N/A"}Else{$Assigned = $ResultB[1]}
    If ($Debug1){Write-Host "Adding result label 2"}
    $objResult2 = New-Object System.Windows.Forms.Label
    $objResult2.Location = New-Object System.Drawing.Size(10,70)
    $objResult2.Size = New-Object System.Drawing.Size(315,20)
    $objResult2.TextAlign = [System.Drawing.ContentAlignment]::MiddleCenter  
    $objResult2.Text = "Assigned to:   $Assigned"
    $objForm.Controls.Add($objResult2)


  
  }ElseIf ($ResultA.count -gt 2){
    $objTextBox.Enabled = $false
    $objTextBox.Text = "       ... More Than One Found ...     "
    $objTextBox.Refresh()
   # $objForm.close()
    [System.Windows.Forms.MessageBox]::Show("$ResultA `n $ResultB","Multiple Logons Found")
    # $objTextBox.Text = "        ... More Than One Found ...     "
  }Else{
    $objTextBox.Text = "          ......  NOT FOUND  ......          "
    $objTextBox.Refresh()
  }
      If ($Debug1){Write-Host "Adding locate another"}
    #$objResult1.Text = "Currently logged onto: "
      $objProcessButton.Text = "Locate Another"
    $objProcessButton.tabindex = 1
    #$objTextBox.Text = " "
    $objTextBox.Enabled = $false
    $objTextBox.Refresh()
    $objProcessButton.Add_Click({$objTextBox.Text = "          ......  RELOADING  ......          ";$objTextBox.Refresh();ResetGUI})





 # $objCloseButton.Location = new-object System.Drawing.Size(($ButtonLeft+60),$ButtonTop)
 # $objForm.Controls.Remove($objProcessButton)
  
  $TargetUserSAM = $Null
  $TargetUserName = $Null
  $Result.Clear()
  $FullArray.Clear()
  $SubArray.Clear()
  $IterationResult.Clear()
  $TargetUser = $False
} #--[ End LocateUser Function ]--


function color-Write
{
    $allColors = ("-Black",   "-DarkBlue","-DarkGreen","-DarkCyan","-DarkRed","-DarkMagenta","-DarkYellow","-Gray",
                  "-Darkgray","-Blue",    "-Green",    "-Cyan",    "-Red",    "-Magenta",    "-Yellow",    "-White")
    $foreground = (Get-Host).UI.RawUI.ForegroundColor # current foreground
    $color = $foreground
    [bool]$nonewline = $false
    $sofar = ""
    $total = ""

    foreach($arg in $args)
    {
        if ($arg -eq "-nonewline") {
        $nonewline = $true
        }elseif ($arg -eq "-foreground"){
            if ($sofar) { Write-Host $sofar -foreground $color -nonewline }
            $color = $foregrnd
            $sofar = ""
        }elseif ($allColors -contains $arg){
            if ($sofar) { Write-Host $sofar -foreground $color -nonewline }
            $color = $arg.substring(1)
            $sofar = ""
        }else{
            $sofar += "$arg "
            $total += "$arg "
        }
    }
    Write-Host $sofar -foreground $color -nonewline:$nonewline
}

Function ResetGUI {
    $objForm.Close()
    $objForm.Dispose()
    CreateGUI
}

Function IsThereText{
  if ($objTextBox.Text.Length -ne 0){
    $objProcessButton.Enabled = $true
  }else{
    $objProcessButton.Enabled = $false
  }
}

Function Relocate {#--------------------[ Assure things run from C:\scripts ]----------------------
  If ($Relocate = False){Break}
  If (!($ScriptHomeDir -eq $TargetPath)){                                                #--[ Paths are NOT same ]--
    If (!(Test-Path -path $TargetPath)){New-Item $TargetPath -type directory}        #--[ Does target path exist?  If not, create it ]--
    If (!(Test-Path "$TargetPath\ScriptFullName" -pathType Leaf )){Copy-Item $ScriptFullPath $TargetPath\$ScriptFullName} #--[ Is the script there?  If not, copy it there ]--

    If (!(Test-Path -path "$Profile\Desktop\$ScriptShortName.lnk")){
    $WshShell = New-Object -comObject WScript.Shell                                    #--[ Place a shortcut on the desktop of current user
    $Shortcut = $WshShell.CreateShortcut("$Profile\Desktop\$ScriptShortName.lnk")
    $Shortcut.TargetPath = 'powershell.exe'
    $Shortcut.Arguments = "-WindowStyle Hidden –Noninteractive -NoLogo -Command `"$TargetPath\$ScriptFullName`""
    $Shortcut.Save()
    If ($Debug1){If (!(Test-Path -path "$Profile\Desktop\$ScriptShortName.lnk")){write-host "FAILED to create icon"}}
    }

    iex -command "$Profile\Desktop\$ScriptShortName.lnk"                                 #--[ re-invoke the script from the new location ]--
    #Remove-Item $MyINvocation.InvocationName                                          #--[ Delete the old copy of this script ]--
    EXIT                                                                              #--[ Force termination ]--
  }Else{                                                                                #--[ Paths ARE good, script exists, OK to execute ]--
    #if (!(Test-Path "$ScriptPath\ThisScriptName"))                                   #--[ an optional additional check ]--
  }
}

Function CreateGUI{
#--------------------------[ Create The GUI ]-----------------------------------
[void] [reflection.assembly]::loadwithpartialname("System.Windows.Forms")
[void] [reflection.assembly]::loadwithpartialname("System.Drawing")
[int]$Width = (Get-WmiObject -Class Win32_DesktopMonitor | Select-Object ScreenWidth,ScreenHeight).ScreenWidth
[int]$FormWidth = 350
[int]$FormHeight = 170
[int]$Center = ($Width / 2)
[int]$ButtonLeft = 55
[int]$ButtonTop = 99

#--[ Create Form ]--
If ($Debug1){Write-Host "Creating form"}
$objForm = new-object System.Windows.Forms.form
$objForm.Text = "User Locatinator.  v$CurrentVersion"
$objForm.size = new-object System.Drawing.Size($FormWidth,$FormHeight)
$objForm.minimumSize = New-Object System.Drawing.Size($FormWidth,$FormHeight)
$objForm.maximumSize = New-Object System.Drawing.Size($FormWidth,$FormHeight)
$objForm.StartPosition = "CenterScreen"
$objForm.KeyPreview = $true
$objForm.Add_KeyDown({if ($_.KeyCode -eq "Enter"){$TargetUser=$objTextBox.Text;$objTextBox.Text = "           ......  Working  ......";$objTextBox.Refresh();LocateUser $TargetUser}})
$objForm.Add_KeyDown({if ($_.KeyCode -eq "Escape"){$objForm.Close()}})

#--[ Add Form Lable ]--
If ($Debug1){Write-Host "Creating label 1"}
$objFormLabelBox = new-object System.Windows.Forms.Label
$objFormLabelBox.Font = new-object System.Drawing.Font("New Times Roman",9,[System.Drawing.FontStyle]::Bold)
$objFormLabelBox.Location = new-object System.Drawing.Size(3,5)
$objFormLabelBox.TextAlign = [System.Drawing.ContentAlignment]::MiddleCenter
$objFormLabelBox.size = new-object System.Drawing.Size(325,20)
$objFormLabelBox.Text = "This tool will locate the PC a user is logged on to:"
$objForm.Controls.Add($objFormLabelBox)

#--[ Add Form Lable ]--
If ($Debug1){Write-Host "Creating label 2"}
$objLabel = New-Object System.Windows.Forms.Label
$objLabel.Location = New-Object System.Drawing.Point(25,25)
$objLabel.Size = New-Object System.Drawing.Size(280,20)
$objLabel.Text = "Enter the First && Last Name, or Logon ID of the user:"
$objLabel.TextAlign = [System.Drawing.ContentAlignment]::MiddleCenter
$objForm.Controls.Add($objLabel)

#--[ Add Text Input Box ]--
If ($Debug1){Write-Host "Creating input box"}
$objTextBox = New-Object System.Windows.Forms.TextBox
$objTextBox.Location = New-Object System.Drawing.Size(78,58)
$objTextBox.Size = New-Object System.Drawing.Size(175,20)
$objTextBox.TabIndex = 0
$objTextBox.add_TextChanged({IsThereText})
$objForm.Controls.Add($objTextBox)

#--[ Add LOCATE Button ]--
If ($Debug1){Write-Host "Adding LOCATE button"}
$objProcessButton = new-object System.Windows.Forms.Button
$objProcessButton.Location = new-object System.Drawing.Size($ButtonLeft,$ButtonTop)
$objProcessButton.Size = new-object System.Drawing.Size(100,25)
$objProcessButton.Enabled = $false
$objProcessButton.Text = "Locate The User"
$objProcessButton.tabindex = 1
$objForm.Controls.Add($objProcessButton)
$objProcessButton.Add_Click({$TargetUser=$objTextBox.Text;$objTextBox.Text = "             ......  Working  ......";$objTextBox.Refresh();LocateUser $TargetUser})

#--[ Add STOP Button ]--
If ($Debug1){Write-Host "Adding STOP button"}
$objCloseButton = new-object System.Windows.Forms.Button
$objCloseButton.Location = new-object System.Drawing.Size(($ButtonLeft+125),$ButtonTop)
$objCloseButton.Size = new-object System.Drawing.Size(100,25)
$objCloseButton.Text = "Cancel/Close"
$objCloseButton.Add_Click({$objForm.close()})
$objCloseButton.tabindex = 2
$objForm.Controls.Add($objCloseButton)

#--[ Open Form ]--
If ($Debug1){Write-Host "Opening form"}
$objForm.topmost = $true
$objForm.Add_Shown({$objForm.Activate();$objTextBox.Focus})
[void]$objForm.ShowDialog()
}

CreateGui

2 comments:

  1. Not working for me. Not returning any results and users I know are logged in.

    ReplyDelete
  2. Did you put the logon/logoff script in place on your domain? The entire process relies on that being in place and functioning. I use this script myself at work and it does work (at least for me). I won't deny that there are still bugs in it but the basic functionality is working when I run it. You can verify that that data is in place in AD by viewing a users settings with the advanced option on so you can see the attribute editor. Assuming the user is currently logged on, the PC they logged onto should be listed in the "managedBy" attribute. If not that data is not being written to AD.

    ReplyDelete