The problem during the quarantine period of the enterprise was as follows: it is really necessary to minimize the number of visits to cabinets by specialists who service and advise on application software, and frankly, users often abuse the help of specialists not wanting to delve into the question itself, saying "they will come - they will help, they will do it, and while I smoke / drink coffee, etc. " Consulting by phone when sharing a server is more effective when viewing a remote screen.
Already after the āinventionā of our bike, sane information turned up on the topic of the article: RDS Shadow - shadow connection to RDP user sessions in Windows Server 2012 R2 or Shadow mode of an unprivileged user in windows server or We delegate the management of RDP sessions . All of them imply the use of a console, even with elements of a simple dialogue.All of the information below is intended for those who normally tolerate abnormal perversions to obtain the desired result, inventing unnecessary methods.In order not to āpull the cat by the tailā, Iāll start with the last one: the bike works for the average user using the AdmiLink utility , for which its author and thank you.I. Console and shadow RDP.Since the use of the Server Manager console with administrator rights -> QuickSessionCollection -> by clicking on the session of the user of interest, selecting Shadow from the context menu for staff instructing in working with the software is not an option, another āwoodenā method was considered, namely:1. We learn the RDP session id:query user | findstr Administrator
or:qwinsta | findstr Administrator
Moreover, " | findstr Administrator " was convenient only when you know what exactly you need Administrator , or use only the first part to see all logged in to the server.
2. We are connected to this session, provided that in the domain group policies the parameter āSets the remote control rules for user sessions of Remote Desktop Servicesā is selected at least āMonitoring the session with user permissionā ( more ):mstsc /shadow:127
Please note that the list will only contain user logins.I repeat that without admin rights you will receive the following:
But for preliminary debugging of the program that will be discussed, I used an account with administrator rights.II. ProgramSo the statement of the problem: the creation of a simple GUI to connect to the user's shadow sense with his permission, sending a message to the user. The programming environment is selected by Lazarus.1. We get the full domain list of users āloginā - āfull nameā from the administrator, or again through the console:wmic useraccount get Name,FullName
no one forbids even this:wmic useraccount get Name,FullName > c:\test\username.txt
I must say right away that it was Lazarus who had the problem with processing this file, since its default encoding is UCS-2, so I just had to manually convert it to regular UTF-8. There are a lot of tabs in the file structure, or rather, a lot of spaces that were decided to be programmed after all, sooner or later the encoding problem will be solved, and the file will be updated programmatically.So, in the idea of āāthe folder, accessible to users of the program, for example c: \ test, in which there will be 2 files: the first with login and fullname, the second with id_rdp and login users. Further we process this data as we can :).In the meantime, to associate with the list of sessions, we transfer this (login and fullname) contents to an array:procedure Tf_rdp.UserF2Array;
var
F:TextFile; i:integer; f1, line1:String; fL: TStringList;
begin
f1:=f_d+'user_name.txt';
fL := TStringList.Create;
fL.Delimiter := '|'; fL.StrictDelimiter := True;
AssignFile(F,f1);
try
reset(F); ReadLn(F,line1);
i:=0;
while not eof(F) do
begin
ReadLn(F,line1);
line1:= StringReplace(line1, ' ', '|',[]);
while pos(' ',line1)>0 do line1:= StringReplace(line1, ' ', ' ', [rfReplaceAll]);
begin
if (pos('|',line1)>0) then
begin
fL.DelimitedText :=line1;
if (fL[0]<>'') then
begin
inc(i);
fam[0,i]:=StringReplace(fL[1],' ','',[rfReplaceall, rfIgnoreCase]);
fam[1,i]:=fL[0];
end;end;end;end;
CloseFile(F);
Fl.Free;
except
on E: EInOutError do ShowMessage(' . : '+E.Message);
end;end;
I apologize for the "lot of code", the following points will be more concise.2. Similarly, using the method from the previous paragraph, we read the result of processing the list into a StringGrid element, while I will give a āsignificantā piece of code:2.1 We get the actual list of RDP sessions in a file:f1:=f_d+'user.txt';
cmdline:='/c query user >'+ f1;
if ShellExecute(0,nil, PChar('cmd'),PChar(cmdline),nil,1)=0 then;
Sleep(500);
2.2 We process the file (only significant lines of code are indicated):StringGrid1.Cells[0,i]:=fL[1]; StringGrid1.Cells[2,i]:=fL[3];
login1:=StringReplace(fL[1],' ','',[rfReplaceall, rfIgnoreCase]);
if (SearchArr(login1)>=0) then
StringGrid1.Cells[1,i]:=fam[1,SearchArr(login1)]
else StringGrid1.Cells[1,i]:='+';
....
if (b_id.Checked=true) then SortGrid(0) else SortGrid(1);
StringGrid1.AutoSizeColumn(0);StringGrid1.AutoSizeColumn(1); StringGrid1.AutoSizeColumn(2);
3. The connection itself when you click on the line with the user and his session number: id:=(StringGrid1.Row);
ids:=StringGrid1.Cells[2,id];
cmdline:='/c mstsc /shadow:'+ ids;
if (b_rdp.Checked=True) then if ShellExecute(0,nil, PChar('cmd'),PChar(cmdline),nil,1) =0 then;
4. A couple more decorations were made, such as sorting by clicking on the radiobutton, and messages to the user or all users.
ā The full source code can be seen hereIII. AdminLink application - what I saw:AdminLink really generates a shortcut that refers to the location of the admilaunch.exe utility and a personal copy of the AdmiRun.Exe launcher that is located in the userās folder, for example, vasya , like C: \ Users \ vasya \ WINDOWS \ . In general, not everything is so bad: you can play with the access rights to the shortcut file and others, to clear your own admin conscience.