Disconnect user from server with ID

Hi there,
I’ve been working on script that will filter out users that have been active in for more than 60min or are disconnected.
For the disconnect script, I used a simple open source script from a MS contributor, found here. The only issue I now have is filtering users based on ID. When my script runs through the server, it does not disconnect the specific user, but rather the entire server. I know the $_.ID variable is key to getting this to work, however; I am not sure how to obtain it from my script values. I’ve included my entire code below, the disconnect properties are at bottom.
Thank you!

function Disconnect-LoggedOnUser {

    param(
        [Parameter(
            Mandatory,
            ValueFromPipeline,
            ValueFromPipelineByPropertyName,
            Position=0
        )]
        [string[]]
            $ComputerName,
        [Parameter(
            Mandatory,
            ValueFromPipelineByPropertyName
        )]
        [int[]]
            $Id
    )

    begin {
        $OldEAP = $ErrorActionPreference
        $ErrorActionPreference = 'Stop'
    }

    process {
        foreach ($Computer in $ComputerName) {
            $Id | ForEach-Object {
                Write-Verbose "Attempting to disconnect session $Id on $Computer"
                try {
                    rwinsta $_ /server:$Computer
                    Write-Verbose "Session $Id on $Computer successfully disconnected"
                } catch {
                    Write-Verbose 'Error disconnecting session displaying message'
                    Write-Warning "Error on $Computer, $($_.Exception.Message)"
                }
            }
        }
    }

    end {
        $ErrorActionPreference = $OldEAP
    }
}






function Convert-QueryToObjects
{
	[CmdletBinding()]
	[Alias('QueryToObject')]
	[OutputType([PSCustomObject])]
	param
	(
		[Parameter(Mandatory = $false,
				   ValueFromPipeline = $true,
				   ValueFromPipelineByPropertyName = $true,
				   Position = 0)]
		[Alias('ComputerName', 'Computer')]
		[string]
		$Name = $env:COMPUTERNAME
	)
	
	Process
	{
		Write-Verbose "Running query.exe against $Name."
		$Users = query user /server:$Name 2>&1
		
		if ($Users -like "*No User exists*")
		{
			# Handle no user's found returned from query.
			# Returned: 'No User exists for *'
			Write-Error "There were no users found on $Name : $Users"
			Write-Verbose "There were no users found on $Name."
		}
		elseif ($Users -like "*Error*")
		{
			# Handle errored returned by query.
			# Returned: 'Error ......'
			Write-Error "There was an error running query against $Name : $Users"
			Write-Verbose "There was an error running query against $Name."
		}
		elseif ($Users -eq $null -and $ErrorActionPreference -eq 'SilentlyContinue')
		{
			# Handdle null output called by -ErrorAction.
			Write-Verbose "Error action has supressed output from query.exe. Results were null."
		}
		else
		{
			Write-Verbose "Users found on $Name. Converting output from text."
			
			# Conversion logic. Handles the fact that the sessionname column may be populated or not.
			$Users = $Users | ForEach-Object {
				(($_.trim() -replace ">" -replace "(?m)^([A-Za-z0-9]{3,})\s+(\d{1,2}\s+\w+)", '$1  none  $2' -replace "\s{2,}", "," -replace "none", $null))
			} | ConvertFrom-Csv
			
			Write-Verbose "Generating output for $($Users.Count) users connected to $Name."
			
			# Output objects.
			foreach ($User in $Users)
			{
				Write-Verbose $User
				if ($VerbosePreference -eq 'Continue')
				{
					# Add '| Out-Host' if -Verbose is tripped.
					[PSCustomObject]@{
						ComputerName = $Name
						Username = $User.USERNAME
						SessionState = $User.STATE.Replace("Disc", "Disconnected")
						SessionType = $($User.SESSIONNAME -Replace '#', '' -Replace "[0-9]+", "")
                        IdleTime = $User.'IDLE TIME'
                        ID = $User.ID
                        LogonTime =$User.'Logon Time'
					} | Out-Host
				}
				else
				{
					# Standard output.
					[PSCustomObject]@{
						ComputerName = $Name
						Username = $User.USERNAME
						SessionState = $User.STATE.Replace("Disc", "Disconnected")
						SessionType = $($User.SESSIONNAME -Replace '#', '' -Replace "[0-9]+", "")
                        IdleTime = $User.'IDLE TIME'
                        LogonTime = $User.'Logon Time'
                        ID = $User.ID
					}
				}
			}
		}
	}
}


$Servers = Get-Content 'H:\demo\computernames.txt'
$openservers =@()
foreach ($Server in $Servers)
{
    if (-not( Test-Connection $Server -Count 1 -Quiet )) { continue }

    if (-not( Convert-QueryToObjects $Server -ErrorAction SilentlyContinue))
    {
     
     $openservers += $server
     $openservers | Out-File 'H:\demo\session\openservers.txt'
 }

    else
    {  
        Convert-QueryToObjects -Name $Server | Where-Object  {@('Active','Disconnected') -contains $_.SessionState} | select @{Name='Server Name';Expression={$_.ComputerName}},
        @{Name='Username'; Expression={$_.Username}}, @{Name='Session State'; Expression={$_.SessionState}}, @{Name='Idle Time'; Expression={$_.IdleTime}}, @{Name='ID'; Expression={$_.ID}},@{Name='Logon Time';Expression={$_.LogonTime}}


    if((Convert-QueryToObjects -Name $Server|?{@('Disconnected') -contains $_.SessionState}) -or (Convert-QueryToObjects -Name $Server|Where-Object{($_.IdleTime -like "*:*") -and ($_.IdleTime -gt "00:59")}))
   

    {

        Disconnect-LoggedOnUser -ComputerName $Server -id 5 -Verbose
        Write-Output "--------------------------------"  

    }

    else { continue}


  }
  }

Does this need to be scripted ? Why not just use group policy?

Computer Configuration| Admin Templates | Windows Components | Remote Desktop Services | Remote Desktop Session Host | Session Time Limits

 User Configuration | Admin Templates | Windows Components | Remote Desktop Services | Remote Desktop Session Host | Session Time Limits

@Simon, thank you for your input, however; I am not permitted to use group policy.

Hey there Russell,

Have you looked at Warren Frame’s Get-UserSession function?

https://gallery.technet.microsoft.com/scriptcenter/Get-UserSessions-Parse-b4c97837

You could probably write it up into a DSC config as well…hmmm…ideas…

Hi Will, thank you for referring to the script.
I’ve actually made a lot of progress since posting here.
Thank you for that, I came up with something a little different. However, I am having issues with the script not overwriting the previous file and it attempting to disconnect the same session multiple times, instead of just once. I know this has to be a simple fix, but I am just not seeing it… thanks!

$Servers = Get-Content 'H:\demo\computernames.txt'
$openservers =@()
foreach ($Server in $Servers)
{
    if (-not( Test-Connection $Server -Count 1 -Quiet )) { continue }

    if (-not( Convert-QueryToObjects $Server -ErrorAction SilentlyContinue))
    {

     $openservers += $server
     $openservers | Out-File 'H:\demo\session\openservers.txt'
 }

    else
    {  
      Convert-QueryToObjects -Name $Server |Where-Object{ {@('Disconnected','Active') -contains $_.SessionState} | Select-Object {@{Name='Server Name';Expression={$_.ComputerName}},
        @{Name='Username'; Expression={$_.Username}}, @{Name='Session State'; Expression={$_.SessionState}}, @{Name='Idle Time'; Expression={$_.IdleTime}}, 
        @{Name='ID'; Expression={$_.ID}} }}| Export-Csv 'H:\demo\session\run11.csv' -NoTypeInformation -Append


    Import-Csv 'H:\demo\session\run11.csv' | Where-Object { ($_.SessionState -eq 'Disconnected') -or (($_.IdleTime -like "*:*") -and ($_.IdleTime -gt "00:59"))} |
    ForEach-Object {
        Disconnect-LoggedOnUser -ComputerName $_.ComputerName -Id $_.ID -Verbose 
    }

   }

   }

You might want to take out the -Append on the Export-Csv on line 18. :slight_smile:

You might want to take out the -Append on the Export-Csv on line 18. :slight_smile: And add -Force to overwrite.

Hi Will,
Thank you for that.

However, as a result of me removing

-append
the only thing exported to the CSV is the list of active users on the last server. The reason I added
-append
originally was b/c I wanted every server/user inputted into the csv.

However, your suggestion did fix the multiple user issue. Thanks!