Problema în perioada de carantină a fost următoarea: este cu adevărat necesar să minimizăm numărul vizitelor în birouri de către specialiștii care asigură și oferă consultanță în domeniul software-ului aplicativ, iar să fim sinceri, utilizatorii deseori abuzează de ajutorul specialiștilor, nereușind să se implice în problemă, spunând „vor veni — vor ajuta — vor rezolva, iar eu între timp mă voi fuma/voi bea o cafea etc.”. Consultarea telefonică cu acces comun la server este mai eficientă dacă se vizualizează ecranul de la distanță.

După „inventarea” bicicletei noastre, a apărut informația relevantă în legătură cu articolul: sau sau . Toate acestea implică utilizarea consolei, chiar și cu elemente de dialog simplu.
Toate informațiile prezentate mai jos sunt destinate celor care suportă normal aberații neobișnuite pentru a obține rezultatul dorit, inventând metode inutile.
Pentru a „nu trage pisica de coadă”, voi începe cu ultimul: bicicleta funcționează pentru utilizatorul obișnuit cu ajutorul utilitarului , pentru care îi mulțumesc autorului.
I. Consola și shadow RDP.
Deoarece utilizarea cu drepturi administrative ale consolei Server Manager -> QuickSessionCollection -> făcând clic pe sesiunea utilizatorului interesat, alegând în meniul contextual Shadow (Copie umbrită) pentru personalul care instruiește în utilizarea software-ului, — nu este o opțiune, s-a analizat o altă metodă „lemnoasă”, și anume:
1. Aflați ID-ul sesiunii RDP:
query user | findstr Administratorsau:
qwinsta | findstr Administrator În plus, "| findstr Administrator" a fost convenabil doar atunci când știi ce anume Administrator îți este necesar, altfel folosești doar prima parte pentru a viziona toți utilizatorii conectați pe server.
![]()
2. Ne conectăm la această sesiune, cu condiția ca în politicile grupurilor de domeniu parametrul „Stabilește regulile de control la distanță pentru sesiunile utilizatorilor ale serviciilor de desktop la distanță” este selectat cu cel puțin „Observația sesiunii cu permisiunea utilizatorului” ():
mstsc /shadow:127Vă rog să luați în considerare că în listă vor fi doar loginurile utilizatorilor.
Repet că fără drepturi administrative veți obține următorul rezultat:

Dar pentru depanarea preliminară a programului despre care va fi vorba, am folosit un cont cu drepturi de administrator.
II. Programul
Așadar, problema este: crearea unei interfețe grafice simple pentru conectarea la senzorul ascuns al utilizatorului, cu acordul acestuia, și trimiterea unui mesaj utilizatorului. Mediul de programare ales este Lazarus.
1. Obținem lista completă de domenii pentru utilizatori «login» — «nume complet» de la administrator sau din nou prin intermediul consolei:
wmic useraccount get Name,FullName nimeni nu interzice chiar și așa:
wmic useraccount get Name,FullName > c:testusername.txtVoi spune de la bun început că Lazarus a avut o problemă cu procesarea acestui fișier, deoarece, în mod implicit, codificarea sa este UCS-2, așa că a fost necesar să-l transform manual în UTF-8 normal. Structura fișierului conține multe tabulări, mai degrabă multe spații, care au fost decise a fi procesate programatic, mai devreme sau mai târziu problema codării va fi rezolvată, iar fișierul va fi actualizat programatic.
Așadar, în planul meu, un folder disponibil pentru utilizatorii programului, de exemplu, c:test, în care vor fi 2 fișiere: primul cu login și nume complet, al doilea cu id_rdp și loginul utilizatorilor. Apoi, procesăm aceste date cum putem:).
Între timp, pentru a asocia cu lista de sesiuni, transferăm acest conținut (login și nume complet) într-un array:
procedure Tf_rdp.UserF2Array;
var
F:TextFile; i:integer; f1, line1:String; fL: TStringList;
begin //f_d cale globală către plasarea fișierelor
f1:=f_d+'user_name.txt'; //sarcina este de a citi conținutul fișierului în array
fL := TStringList.Create; // vom supune șirul unor metamorfize cu separatoare
fL.Delimiter := '|'; fL.StrictDelimiter := True;
AssignFile(F,f1);
try // Deschide fișierul pentru citire
reset(F); ReadLn(F,line1);
i:=0;
while not eof(F) do // Citim liniile, până nu se termină fișierul
begin
ReadLn(F,line1);
line1:= StringReplace(line1, ' ', '|',[]); //înlocuim primul găsit 2 spații cu separator |
// eliminăm toate spațiile duble
while pos(' ',line1)>0 do line1:= StringReplace(line1, ' ', ' ', [rfReplaceAll]);
begin
if (pos('|',line1)>0) then
begin // dacă separatorul există, îl aducem în array
fL.DelimitedText :=line1; // împărțim în coloane
if (fL[0]'') then // dacă contul are nume
begin // introducem-l în array
inc(i); // scăpăm de posibilele spații unice în login
fam[0,i]:=StringReplace(fL[1],' ','',[rfReplaceall, rfIgnoreCase]);
fam[1,i]:=fL[0];
end;end;end;end; // Gata. Închidem fișierul.
CloseFile(F);
Fl.Free;
except
on E: EInOutError do ShowMessage('Eroare de procesare a fișierului. Detalii: '+E.Message);
end;end;Îmi cer scuze pentru «mult cod», punctele următoare vor fi mai concise.
2. Similar to the method from the previous point, we read the processing result of the list into a StringGrid element, while presenting a 'significant' piece of code:
2.1 We obtain the current 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); \/\/ you can wait longer while the file is created for reading2.2 We process the file (only significant lines of code are shown):
StringGrid1.Cells[0,i]:=fL[1]; StringGrid1.Cells[2,i]:=fL[3]; \/\/ cycling into StringGrid1
login1:=StringReplace(fL[1],' ','',[rfReplaceall, rfIgnoreCase]); \/\/ remove spaces from the login
if (SearchArr(login1)>=0) then \/\/ search in the array from p1. login and record in the table full name
StringGrid1.Cells[1,i]:=fam[1,SearchArr(login1)]
else StringGrid1.Cells[1,i]:='+'; \/\/ either record a plus sign:)
.... \/\/ depending on the user's choice, we sort and format the data
if (b_id.Checked=true) then SortGrid(0) else SortGrid(1);
StringGrid1.AutoSizeColumn(0);StringGrid1.AutoSizeColumn(1); StringGrid1.AutoSizeColumn(2); 3. The actual connection is made by clicking on the row with the user and the number of their session:
id:=(StringGrid1.Row);\/\/ get the row number IntToStr(StringGrid1.Row)
ids:=StringGrid1.Cells[2,id]; \/\/ get the rdp identifier
cmdline:='\/c mstsc \/shadow:'+ ids; \/\/ and connect....
if (b_rdp.Checked=True) then if ShellExecute(0,nil, PChar('cmd'),PChar(cmdline),nil,1) =0 then; 4. A couple more embellishments have been made, such as sorting by clicking on the radiobutton, and messages to the user or all users.

→ The full source code can be seen
III. Using AdminLink — what I observed:
AdminLink indeed generates a shortcut that points to the location of the utility admilaunch.exe, and a personal copy of the launch utility AdmiRun.Exe which is located in the user's folder, for example vasya, like C:UsersvasyaWINDOWS. Overall, it's not so bad: with file shortcut permissions and others, to clear one's administrative conscience, you can play around.
Sursa: habr.com
