Link to home
Start Free TrialLog in
Avatar of Hiep Nguyen
Hiep Nguyen

asked on

script

Hello,

I'm looking for help to write a script to find out who are on the RDS and sign some of them off.  I like to use Task Schedule to run this script.  Currently, I run shutdown.exe /f /l to sign out individual user.  There must be a way to find out a list of users on the server and loop-through one by one to force them off the server.  Thank you.
Avatar of skullnobrains
skullnobrains

psloggedon ( which you can download from the technet ) will display such a list remotely. i believe you can loop through the output and logout whoever you want using your existing command.

there is also a powershell way as well, and most likely something using the output of the existing "net" command. those should be rather easy to find online. i'll help with the crafting if you go that way.

also note that the windows management console has a snippet that allows to do such tasks graphically, though i'm unsure you can filter and logout a list of users easily
Try this PowerShell script; works remotely, can process multiple servers.
It's in test mode, the actual logoff part is commented out so that you can test it.
Function Get-UserSession {
[CmdletBinding()]
Param(
	[Parameter(Position=0, ValueFromPipeline=$true)]
	[String[]]$ComputerName = $ENV:ComputerName
)
	Process {
		$ComputerName | ForEach-Object {
			Try {
				Write-Verbose "Processing $($_) ..."
				$out = [ordered]@{}
				$out['ComputerName'] = $_
				$out['IPAddress'] = ([System.Net.Dns]::GetHostAddresses($_) | Where-Object {$_.AddressFamily -eq 'InterNetwork'})[0].IPAddressToString
				$output = & 'C:\Windows\system32\query.exe' user /server:$_ 2>&1
				If ($output -like '*No User exists*') {
					Write-Warning "[$($_)] $($output -join ' ')"
				} ElseIf ($output -like '*Error*') {
					Throw "[$($_)] $($output -join ' ')"
				} Else {
					$output |
						Select-Object -Skip 1 |
						Where-Object {$_ -match '(?<Current>.)(?<UserName>.{20})(?<SessionName>.{16})(?<ID>.{7})(?<State>.{8})\s+(?<IdleTime>\S+)\s+(?<LogonTime>.*)'} |
							ForEach-Object {
								ForEach ($prop in 'UserName', 'SessionName', 'ID', 'State', 'IdleTime', 'LogonTime') {$out[$prop] = $Matches[$prop].Trim()}
								[PSCustomObject]$out
							}
				}
			} Catch {
				$PSCmdlet.WriteError($_)
			}
		}
	}
}

$servers = "Server1", "Server2", "Server3"
$servers |
	Get-UserSession |
	ForEach-Object {
		Write-Host "Logging off $($_.UserName) from $($_.ComputerName)"
	#	& logoff.exe $_.SessionName /SERVER:$_.ComputerName
	}

Open in new window

Avatar of Hiep Nguyen

ASKER

I copied to notepad and saved to logoff.vbs, then run it but I got error.
User generated image
Again: this is a PowerShell script.
Save as .ps1 and run from a PowerShell console.
Yeah, you need to save the text file with the PS1 extension (it's not a .vbs) because the solution given by odba is Powershell and not a visual basic script).

https://prnt.sc/sjqxo8
... or run "ps c:\path\to\whatever"
Thank you for your help.

How I debug or pause at each user to examine?
Should I run this on domain controller or on each RDS?
Not my script but it seems to process a list of rdp servers so it should work pretty much anywhere on a domain providing you have adequate (admin ) privileges
You can run that on any machine remotely against a list of other machines.
Define the list of servers to run it against in line 35.
You can enter a line like
		Read-Host 'Return to continue'

Open in new window

between lines 39 and 40 to stop for each user.

And as I said, the script is in test mode and will not logoff anyone as long as line 40 stays commented.
Try "ps logoff.ps1"
ps : Cannot find a process with the name "logoff.ps1". Verify the process name and call the cmdlet again.
At line:1 char:1
+ ps logoff.ps1
+ ~~~~~~~~~~~~~
    + CategoryInfo          : ObjectNotFound: (logoff.ps1:String) [Get-Process], ProcessCommandException
    + FullyQualifiedErrorId : NoProcessFoundForGivenName,Microsoft.PowerShell.Commands.GetProcessCommand

then try ".\logoff.ps1"
Get-UserSession : Exception calling "GetHostAddresses" with "1" argument(s): "No such host is known"
At C:\Users\Administrator\Desktop\logoff.ps1:37 char:2
+     Get-UserSession |
+     ~~~~~~~~~~~~~~~
    + CategoryInfo          : NotSpecified: (:) [Get-UserSession], MethodInvocationException
    + FullyQualifiedErrorId : SocketException,Get-UserSession
Is there a command (from cmd) to list all users currently on the server?  I don't need to do this remotely.
You don't pass a server name to the function:
Function Get-UserSession {
[CmdletBinding()]
Param(
	[Parameter(Position=0, ValueFromPipeline=$true)]
	[String[]]$ComputerName = $ENV:ComputerName
)
	Process {
		$ComputerName | ForEach-Object {
			Try {
				Write-Verbose "Processing $($_) ..."
				$out = [ordered]@{}
				$out['ComputerName'] = $_
				$out['IPAddress'] = ([System.Net.Dns]::GetHostAddresses($_) | Where-Object {$_.AddressFamily -eq 'InterNetwork'})[0].IPAddressToString
				$output = & 'C:\Windows\system32\query.exe' user /server:$_ 2>&1
				If ($output -like '*No User exists*') {
					Write-Warning "[$($_)] $($output -join ' ')"
				} ElseIf ($output -like '*Error*') {
					Throw "[$($_)] $($output -join ' ')"
				} Else {
					$output |
						Select-Object -Skip 1 |
						Where-Object {$_ -match '(?<Current>.)(?<UserName>.{20})(?<SessionName>.{16})(?<ID>.{7})(?<State>.{8})\s+(?<IdleTime>\S+)\s+(?<LogonTime>.*)'} |
							ForEach-Object {
								ForEach ($prop in 'UserName', 'SessionName', 'ID', 'State', 'IdleTime', 'LogonTime') {$out[$prop] = $Matches[$prop].Trim()}
								[PSCustomObject]$out
							}
				}
			} Catch {
				$PSCmdlet.WriteError($_)
			}
		}
	}
}

Get-UserSession |
	ForEach-Object {
		Write-Host "Logging off $($_.UserName) from $($_.ComputerName)"
	#	& logoff.exe $_.SessionName /SERVER:$_.ComputerName
	}

Open in new window

Thanks everyone.  I'm going to write a simple program to do this using 2 DOS commands:

query user
logoff

With shutdown /f /l, it will close all opening programs/files log off the user without any warning.
Can I really do the same with logoff command?
shutdown can display a warning and allow a grace time. just use /t DELAY in the command. i am unsure logoff can do the same.

the tool i pointed above allows to list users locally or remotely. the output should be reasonably easy to process in a batch file
https://docs.microsoft.com/en-us/windows-server/administration/windows-commands/query-user 

Does any one know the size for each column "query user" returns?
i would expect it to change based on the length of the  user names. let's hope i am wrong ;)
the psloggedon tool i mentionned above should be more script-friendly if you can use it
odba's script seems ok to me as well
Why do you want to reinvent the wheel? My function above already parses this exact output.
I'm not allow to run .ps1/.bat/.vbs (a lot more) on our system.
How do you expect to run anything ? Better tell us what IS available. From above comments, i was assuming you wanted a batch script.

Btw, whoever expects to achieve security with such limitations should probably get a different job asap.
ASKER CERTIFIED SOLUTION
Avatar of Hiep Nguyen
Hiep Nguyen

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
So you are not allowed to run scripts, but you're expected to administer the RDS servers? And you can introduce and run your own executables? Wow. Just ... wow.
Anyway, PowerShell can easily send mails using Send-MailMessage.
And if whatever language you're using to create the program supports RegEx, you can just use the one from line 22.
Or try to "compile" your PowerShell scripts: https://github.com/MScholtes/PS2EXE

Can you successfully run something like the following:
powershell.exe -NoExit -Command "gci C:\Windows"

Open in new window

try .cmd with a batch file syntax. it will likely work.

use a dummy extension such as zzz and open it with "cmd". i am unsure but this probably also works with powershell.

you can also try various hacks such as spawning your script using the start command in a separate window, using an autoexec file in a regular directory, creating a shell extension if you can access the registry ...

windows execution protection by extension name is something you can bypass easily if you just mess a little around.

worst case scenario, you can install a separate interpreter such as bash or perl.
As an admin, of course I can run anything but the plan is to give the *program* to user to run probably in task scheduler.  Another concern is user can modify any script and it becomes a security issue.  That's reason why I don't want to use script in this case.  I really appreciate your help and input.
Non admin users will no be able to modify the script if you setup adequate privileges. But they wont be able to run it as admin either unless you both code something and give them special privileges. My windows days are old but i recall impersonate client from thread.

If your users can run preconfigured scheduled tasks as admin, that should work. Likewise a service can do the trick if they are allowed to start them. Just make sure they cannot edit the script or run a different one.
I got it to work the way that I wanted.  Thank you.
selected solution adds complexity, a false assertion, and does not answer the initial question in any way. mostly, it is a repetition of the question.