Tuesday, January 3, 2012

Powershell - Function: Get-LoggedOnUsers

While trying to come up with a script for on of the TechNet Script Center Repository request queue:
I saw this one:
Identify open User sessions on all domain servers
Geistvolf

Check domain Servers daily for open user sessions. Computer Management:System tools>Shared Folders>sessions takes a long time. We must identify open user sessions and choose sessions we need to terminate.
As I worked on the request I had to hit the boards a few times, but, I eventually cobbled together this to drag out all logged on users:
function Get-LoggedOnUser {
[CmdletBinding()]
param(
[Parameter(
Mandatory=$true
)]
$computerName
)

Get-WmiObject -ComputerName $computerName -Class Win32_LoggedOnUser | `
Select-Object @{Expression={$_.__SERVER}; Name="Server"}, `
@{Expression={$_.Antecedent.ToString().Split("=")[2].Replace("`"","")};Name="User"};
}
This raw function uses the Get-WmiObject cmdlet to access the Win32_LoggedOnUser. I tried this location at first by omitting the -ComputerName parameter, but, found I needed it as I attempted to solve the second portion of the request: "sessions on ALL domain servers". Some of the biggest issues I had revolved around:
  • machine accessibility and RPC issues
  • stale AD entries
As I pulled some other scripts to compile this one it grew fairly quickly.  When you run the script above it outputs the following:
Server    User
--------  --------
MyMachine Will
By combing this basic logic with the Scripting Guys post I arrived at this final script:
<#
.AUTHOR
Will Steele (wlsteele@gmail.com)

.DEPENDENCIES
n/a

.DESCRIPTION
This script searches the current domain and returns all currently logged in
users for the machines with a valid RPC (port 135) connection. If a
connection cannot be formed, the machine name will be returned with an error
message.

.EXAMPLE
n/a

.EXTERNALHELP
None.

.FORWARDHELPTARGETNAME
None.

.INPUTS
System.Object

.LINK
http://technet.microsoft.com/en-us/library/ff730959.aspx.

.NAME
Get-UsersLoggedOnDomain

.NOTES
If stale entries exist in AD or the local machine cache the script will error
when it encounters these machine, but, the script will continue processing.

.OUTPUTS
System.Array

.SYNOPSIS
Returns a list of logged on user sessions for the current domain.
#>

#region functions

function Get-LoggedOnUsers {
[CmdletBinding()]
param(
[Parameter(
Mandatory=$true
)]
$computerName
)

Try {
Get-WmiObject -ComputerName $computerName -Class Win32_LoggedOnUser | Select-Object @{Expression={$_.__SERVER}; Name="Server"}, @{Expression={$_.Antecedent.ToString().Split("=")[2].Replace("`"","")};Name="User"} -ErrorAction Stop;
}
Catch {
Write-Output "$computerName was not accessible.";
}
}

function Get-PingStatus {
[Cmdletbinding()]
param (
[Parameter(
Mandatory=$false,
Position=0,
ValueFromPipeline=$true
)]
[System.String] $computerName = $env:COMPUTERNAME
)

$ping = Get-WmiObject -Query "SELECT * FROM Win32_PingStatus WHERE Address='$computerName'"

if ($ping.StatusCode -eq 0) {
return $true;
} else {
return $false;
}
}

function Test-Port {
Param(
[string] $srv,
$port=135,
$timeout=3000,
[switch]$verbose
)

# Test-Port.ps1
# Does a TCP connection on specified port (135 by default)

$ErrorActionPreference = "SilentlyContinue"

# Create TCP Client
$tcpclient = new-Object system.Net.Sockets.TcpClient

# Tell TCP Client to connect to machine on Port
$iar = $tcpclient.BeginConnect($srv,$port,$null,$null)

# Set the wait time
$wait = $iar.AsyncWaitHandle.WaitOne($timeout,$false)

# Check to see if the connection is done
if(!$wait)
{
# Close the connection and report timeout
$tcpclient.Close()
if($verbose){Write-Host "Connection Timeout"}
Return $false
}
else
{
# Close the connection and report the error if there is one
$error.Clear()
$tcpclient.EndConnect($iar) | out-Null
if(!$?){if($verbose){write-host $error[0]};$failed = $true}
$tcpclient.Close()
}

# Return $true if connection Establish else $False
if($failed){
return $false
} else {
return $true
}
}

#endregion functions

#region script body

$strFilter = "computer"
$objDomain = New-Object System.DirectoryServices.DirectoryEntry
$objSearcher = New-Object System.DirectoryServices.DirectorySearcher
$objSearcher.SearchRoot = $objDomain
$objSearcher.SearchScope = "Subtree"
$objSearcher.PageSize = 1000

$objSearcher.Filter = "(objectCategory=$strFilter)"
$colResults = $objSearcher.FindAll()

# Create empty arrays for storing results
$errored = $results = $null;

# Return logged on users based on computer name
foreach ($i in $colResults) {
Write-Output "Processing $($i.GetDirectoryEntry().Name)";
if((Test-Port -srv $i.GetDirectoryEntry().Name) -and (Get-PingStatus -computerName $i.GetDirectoryEntry().Name)) {
Try {
$results += Get-LoggedOnUsers -computerName $i.GetDirectoryEntry().Name;
}
Catch {
Write-Output "$($i.GetDirectoryEntry().Name) was not accessible.";
}
} else {
Write-Output "$($i.GetDirectoryEntry().Name) cannot be accessed over port 135.";
}
}

# Output results.
Write-Output "Successfully processed machines:"
return $results;

#endregion script body
I added it to the Technet repository:
http://gallery.technet.microsoft.com/scriptcenter/Get-LoggedOnDomainUsersps1-f6775285

0 comments:

Post a Comment