Author Posts

July 10, 2017 at 11:38 pm

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}


  }
  }

July 11, 2017 at 8:32 am

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

July 11, 2017 at 9:48 am

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

July 11, 2017 at 2:38 pm

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...

July 11, 2017 at 2:46 pm

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 
    }

   }

   }

July 11, 2017 at 3:16 pm

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

July 11, 2017 at 3:19 pm

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

July 11, 2017 at 3:30 pm

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!