El problema durante el trabajo de cuarentena de la empresa fue el siguiente: realmente es necesario minimizar la cantidad de visitas a las oficinas por parte de los especialistas que atienden y consultan sobre software aplicado, y para ser sinceros, los usuarios a menudo abusan de la ayuda de los especialistas sin querer profundizar en la cuestión, diciendo: "vendrán, ayudarán, harán, y yo mientras tanto fumaré/tomaré un café, etc.". La consulta telefónica con acceso conjunto al servidor es más efectiva si se visualiza la pantalla remota.

Ya después de "inventar" nuestra bicicleta, apareció información razonable sobre el tema del artículo: o o . Todos implican el uso de consola, incluso con elementos de un diálogo simple.
Toda la información presentada a continuación está destinada a aquellos que soportan normalidad en las anormalidades para obtener el resultado deseado, inventando formas innecesarias.
Para "no alargar el asunto", comenzaré desde el final: la bicicleta funciona para un usuario común mediante la herramienta , por lo cual, gracias a su autor.
I. Consola y shadow RDP.
Dado que el uso de la consola Server Manager con derechos de administrador -> QuickSessionCollection -> haciendo clic en la sesión del usuario de interés, seleccionando en el menú contextual Shadow (Copia sombra) para el personal que instruye sobre el uso del software, no era una opción, se consideró otro método "rudimentario", a saber:
1. Averiguar el ID de sesión RDP:
query user | findstr Administratoro:
qwinsta | findstr Administrator Sin embargo, "| findstr Administrator" era conveniente solo cuando sabes que Administrator es lo que necesitas, o usar solo la primera parte para visualizar todos los que han iniciado sesión en el servidor.
![]()
2. Nos conectamos a esta sesión, siempre que en las políticas de grupo de dominio la opción "Establece reglas de control remoto para sesiones de usuario de los servicios de escritorio remoto" esté seleccionada al menos como "Observación de la sesión con el consentimiento del usuario" ( mstsc /shadow:127):
Por favor, ten en cuenta que en la lista solo aparecerán los inicios de sesión de los usuarios.Reitero que sin derechos de administrador obtendrás lo siguiente:
Reitero que sin derechos de administrador obtendrás lo siguiente:

Pero para la depuración preliminar del programa del que hablaremos, utilicé una cuenta con derechos de administrador.
II. Programa
Así que el planteamiento del problema es: crear una interfaz gráfica simple para conectarse al sensor oculto del usuario con su permiso y enviar un mensaje al usuario. El entorno de programación elegido es Lazarus.
1. Obtenemos la lista completa de dominios de usuarios «login» — «nombre completo» desde el administrador, o de nuevo a través de la consola:
wmic useraccount get Name,FullName nadie prohíbe hacerlo así:
wmic useraccount get Name,FullName > c:testusername.txtDebo decir que fue en Lazarus donde encontré un problema al procesar este archivo, ya que por defecto su codificación es UCS-2, por lo que tuve que convertirlo manualmente a UTF-8. La estructura del archivo contiene muchas tabulaciones, más bien muchos espacios, que se decidió procesar programáticamente; tarde o temprano se resolverá el problema de la codificación y el archivo se actualizará automáticamente.
Así que, en la idea, hay una carpeta accesible para los usuarios del programa, por ejemplo c:test, que contendrá 2 archivos: el primero con el login y el nombre completo, y el segundo con el id_rdp y el login de los usuarios. Luego, procesamos estos datos como podemos:).
Mientras tanto, para asociar con la lista de sesiones, trasladamos este contenido (login y nombre completo) a un 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('Error al procesar el archivo. Detalles: '+E.Message);
end;end;Pido disculpas por el «mucho código», los siguientes puntos serán más concisos.
2. De manera similar al método del punto anterior, leemos el resultado del procesamiento de la lista en el elemento StringGrid, proporcionando a continuación un fragmento de código "significativo":
2.1 Obtenemos la lista actual de sesiones RDP en un archivo:
f1:=f_d+'user.txt';
cmdline:='\/c query user >'+ f1;
if ShellExecute(0,nil, PChar('cmd'),PChar(cmdline),nil,1)=0 then;
Sleep(500); \/\/ también se puede esperar un poco más mientras se crea el archivo para lectura2.2 Procesamos el archivo (solo se indican las líneas de código significativas):
StringGrid1.Cells[0,i]:=fL[1]; StringGrid1.Cells[2,i]:=fL[3]; \/\/ se agrega en un ciclo a StringGrid1
login1:=StringReplace(fL[1],' ','',[rfReplaceall, rfIgnoreCase]); \/\/ eliminamos los espacios en el login
if (SearchArr(login1)>=0) then \/\/ buscamos en el arreglo del p1. login y grabamos en la tabla el nombre completo
StringGrid1.Cells[1,i]:=fam[1,SearchArr(login1)]
else StringGrid1.Cells[1,i]:='+'; \/\/ o registramos un más:)
.... \/\/ según la elección del usuario, ordenamos y formateamos los datos
if (b_id.Checked=true) then SortGrid(0) else SortGrid(1);
StringGrid1.AutoSizeColumn(0);StringGrid1.AutoSizeColumn(1); StringGrid1.AutoSizeColumn(2); 3. La conexión en sí al hacer clic en la fila con el usuario y su número de sesión:
id:=(StringGrid1.Row);\/\/ obtenemos el número de fila IntToStr(StringGrid1.Row)
ids:=StringGrid1.Cells[2,id]; \/\/ obtenemos el identificador rdp
cmdline:='\/c mstsc \/shadow:'+ ids; \/\/ y nos conectamos....
if (b_rdp.Checked=True) then if ShellExecute(0,nil, PChar('cmd'),PChar(cmdline),nil,1) =0 then; 4. Se han hecho un par de decoraciones adicionales como ordenar al hacer clic en el radiobutton y mensajes al usuario, o a todos los usuarios.

→ El código fuente completo se puede ver
III. Aplicación de AdminLink — lo que vi:
AdminLink realmente genera un acceso directo que referencia la ubicación de la utilidad admilaunch.exe, y una copia personal de la utilidad de inicio AdmiRun.Exe que se encuentra en la carpeta del usuario, por ejemplo vasya, tipo C:UsersvasyaWINDOWS. En general, no todo está tan mal: con los derechos de acceso al archivo del acceso directo y otros, para limpiar nuestra conciencia administrativa, se puede jugar.
Fuente: habr.com
