Una mejor manera de encontrar a un usuario conectado de forma remota usando PowerShell

Hay un montón de maneras diferentes de conseguir usuarios conectados, pero tengo una favorita! Usando qwinsta, el único problema es que devuelve una cadena y a PowerShell le gustan los objetos: (¡Pero no temas! Podemos arreglarlo.

qwinsta

Prereqs

Me encontré con un problema de error 5 de acceso denegado cuando empecé a usar qwinsta, hay una solución de registro para eso (gracias StackOverflow:

"AllowRemoteRPC"=dword:00000001

Dado que este es un blog de PowerShell, aquí está la sintaxis de PowerShell para agregarlo a un equipo remoto, ya que es probable que trabajemos en equipos remotos.

$ComputerName = 'Computer' #replace with the computer name$LMtype = ::LocalMachine$LMkey = "SYSTEM\CurrentControlSet\Control\Terminal Server"$LMRegKey = ::OpenRemoteBaseKey($LMtype,$ComputerName)$regKey = $LMRegKey.OpenSubKey($LMkey,$true)If($regKey.GetValue("AllowRemoteRPC") -ne 1){ $regKey.SetValue("AllowRemoteRPC",1) Start-Sleep -Seconds 1}$regKey.Dispose()$LMRegKey.Dispose()

Sintaxis

La sintaxis de qwinsta es bastante sencilla:

qwinsta /server:ServerName

Y la salida se ve bastante desenfadada:

 SESSIONNAME USERNAME ID STATE TYPE DEVICE services 0 Disc console Anthony 1 Active rdp-tcp 65536 Listen

El único problema es que si estaba buscando un servidor RDS, no hay una forma fácil de filtrar cadenas en PowerShell. Así que no podías hacer algo como:

qwinsta /server:RDSServer | Where-Object USERNAME -like "Anth*"

Pero podemos arreglar eso! Permite envolver qwinsta para que podamos objetivar esa salida!

Envolver qwinsta

Vamos a crear una función de PowerShell aquí, tenga cuidado con el lector.

Retorcer la cadena para sus valores

Además de todo el material de parámetros, al que llegaremos, lo primero que haremos es dirigir la salida a una variable:

$result = qwinsta /server:$ComputerName

Ahora $result se verá igual que la salida de antes, pero se formateará como una matriz de cadenas (string). Podemos, por supuesto, comprobar que con Get-Member:

($result | Get-Member).TypeName

Así que la próxima cosa es recuperar la información que queremos. He decidido un bucle for, saltándome la primera línea ya que no necesitamos los encabezados:

ForEach($line in $result)

Para cada línea, ¿con qué está delimitado cada valor? Espacios! Así que podemos usar el .Método Split() en cadenas y dividir con cada espacio. La única advertencia es que también necesitaremos probar el valor en cada división, ya que puede dividir varios espacios y obtener valores nulos.

$tmp = $line.split(" ") | ?{$_.length -gt 0}

Una cosa a tener en cuenta es que la salida de qwinsta es de ancho fijo, lo que significa que el ancho no cambia independientemente de la longitud de ninguno de los valores. Podemos aprovechar eso y usar ciertos índices a nuestro favor.

Lo primero que hay que hacer es determinar qué líneas contienen realmente a un usuario. Podemos contar caracteres y encontrar que el valor del nombre de usuario comienza en el carácter 19 (el primer carácter es en realidad el espacio, mire con cuidado). Así que si el carácter 19 no está vacío, tenemos un usuario.

If(($line -ne " "))

Lo siguiente que noté en testing con qwinsta es que la propiedad SessionName solo tiene valor si la sesión está en el estado’ Activo’. Así que también necesitamos verificar si el Estado está ‘Activo’, lo que podemos hacer comprobando el índice de ese estado para un ‘A’

If($line -eq "A")

Si está “Activa”, la variable $tmp tendrá diferentes cantidades de valores.

Activo:

#raw rdp-tcp#0 anthony 2 Active #$tmp variable (split)rdp-tcp#0anthony2Active

No Se Activa:

#raw anthony 2 Disc #$tmp variable (split)anthony2Disc

Convertir los valores en objetos utilizables

Una vez que tengamos todos los valores calculados, podemos crear un nuevo objeto PSOB, ¡mi favorito! Si tiene PowerShellv5, puede usar el acelerador de tipo {} (x), pero usaré el cmdlet New-Object aquí para obtener la máxima compatibilidad.

New-Object PSObject -Property @{ "ComputerName" = $ComputerName "SessionName" = $tmp "UserName" = $tmp "ID" = $tmp "State" = $tmp "Type" = $tmp}

En este caso, ya que sabemos que la sesión está activa, la primera cadena en $tmp es el nombre de la sesión, y luego, en orden, tiene el resto de las propiedades. Es posible que’ Type ‘o’ SessionName ‘ vuelvan vacíos.

Si el estado no está “Activo”, lo cambiaríamos a:

New-Object PSObject -Property @{ "ComputerName" = $ComputerName "SessionName" = $null "UserName" = $tmp "ID" = $tmp "State" = $tmp "Type" = $null}

El producto final

Dado que esto no es una publicación sobre la creación de funciones, voy a saltarme la otra sintaxis y mostrarte la función final que escribí. Esto incluye algunas prácticas recomendadas, como verificar la conectividad y los derechos de administrador antes de entrar en las cosas emocionantes.

Function Get-ActiveSessions{ Param( $Name , $Quiet ) Begin{ $return = @() } Process{ If(!(Test-Connection $Name -Quiet -Count 1)){ Write-Error -Message "Unable to contact $Name. Please verify its network connectivity and try again." -Category ObjectNotFound -TargetObject $Name Return } If(((::GetCurrent()).groups -match "S-1-5-32-544")){ #check if user is admin, otherwise no registry work can be done #the following registry key is necessary to avoid the error 5 access is denied error $LMtype = ::LocalMachine $LMkey = "SYSTEM\CurrentControlSet\Control\Terminal Server" $LMRegKey = ::OpenRemoteBaseKey($LMtype,$Name) $regKey = $LMRegKey.OpenSubKey($LMkey,$true) If($regKey.GetValue("AllowRemoteRPC") -ne 1){ $regKey.SetValue("AllowRemoteRPC",1) Start-Sleep -Seconds 1 } $regKey.Dispose() $LMRegKey.Dispose() } $result = qwinsta /server:$Name If($result){ ForEach($line in $result){ #avoiding the line 0, don't want the headers $tmp = $line.split(" ") | ?{$_.length -gt 0} If(($line -ne " ")){ #username starts at char 19 If($line -eq "A"){ #means the session is active ("A" for active) $return += New-Object PSObject -Property @{ "ComputerName" = $Name "SessionName" = $tmp "UserName" = $tmp "ID" = $tmp "State" = $tmp "Type" = $tmp } }Else{ $return += New-Object PSObject -Property @{ "ComputerName" = $Name "SessionName" = $null "UserName" = $tmp "ID" = $tmp "State" = $tmp "Type" = $null } } } } }Else{ Write-Error "Unknown error, cannot retrieve logged on users" } } End{ If($return){ If($Quiet){ Return $true } Else{ Return $return } }Else{ If(!($Quiet)){ Write-Host "No active sessions." } Return $false } }}

Get-ActiveSessions

Puedes encontrar esto en mi repositorio de Utilidades de GitHub. ¡Este es mi primer guión publicado! ¡Hurra! Así que repasemos cómo usarlo:

Uso simple

Para recuperar los usuarios que han iniciado sesión en una computadora remota o local, simplemente usaríamos:

Get-ActiveSessions ComputerName

Y esto debería devolver algo similar a:

ID : 2SessionName :Type :UserName : anthonyComputerName : dc01State : Disc

Y si lo pasa a una variable y mira el nombre del tipo, deberíamos tener un PSCustomObject, lo que significa que podemos usar todos los demás cmdlets útiles para filtrar y demás.

$sessions = Get-ActiveSessions ComputerName($sessions | Get-Member).TypeNameSystem.Management.Automation.PSCustomObject

Uso avanzado

Tenemos dos cosas a nuestro favor aquí, podemos aceptar entrada de canalización Y genera un objeto. Así que podemos hacer cosas antes y después Get-ActiveSessions.

¿Qué pasa si desea encontrar todos los servidores de su entorno que tienen un usuario en particular conectado a ellos?

Get-ADComputer -Filter {OperatingSystem -like '*Server*'} | Get-ActiveSessions | Where-Object UserName -eq 'Anthony'

¿O qué tal extraer una lista de computadoras de un archivo de texto y generar la lista de usuarios únicos que han iniciado sesión en todos ellos?

get-content C:\temp\computers.txt | Get-ActiveSessions | Select-Object -Unique UserName -ExpandProperty UserName | Out-File C:\temp\users.txt

Si se te ocurre algún uso útil de Get-ActiveSessions, ¡comenta!

Conclusión

Espero que aprecies por qué escribiría una función para hacer esto por mí. Honestamente, no pude contar el número de veces que he usado esto para hacer mi trabajo más fácil.

De todos modos, tengo un par de publicaciones notables por venir, particularmente el socio de Get-ActiveSessions: Close-ActiveSessions.

Spoiler:

Get-ActiveSessions Computer | Close-ActiveSessions

Y también: ‘Creación de tareas programadas para Scripts de PowerShell’, que será un viaje ya que aún no he hecho ninguna imagen!

Leave a Reply