Powershell - how to check logged users in specific Machines - powershell

I have a code to check on local machines logged sessions as below
Get-WmiObject win32_networkloginprofile | ? {$_.lastlogon -ne $null} | % {[PSCustomObject]#{User=$_.caption; LastLogon=[Management.ManagementDateTimeConverter]::ToDateTime($_.lastlogon)}}
Is it possible to check it for specific machines a logged sessions , even the one that have status "disconnected" ?

Obviously you need rights on the target computers:
Function Get-LoggedOnUser {
param(
[CmdletBinding()]
[Parameter(ValueFromPipeline=$true,
ValueFromPipelineByPropertyName=$true)]
[string[]]$ComputerName = 'localhost'
)
begin {
$ErrorActionPreference = 'Stop'
}
process {
foreach ($Computer in $ComputerName) {
try {
quser /server:$Computer 2>&1 | Select-Object -Skip 1 | ForEach-Object {
$CurrentLine = $_.Trim() -Replace '\s+',' ' -Split '\s'
# If session is disconnected different fields will be selected
if ($CurrentLine[2] -eq 'Disc') {
[pscustomobject]#{
UserName = $CurrentLine[0];
ComputerName = $Computer;
SessionName = $null;
Id = $CurrentLine[1];
State = $CurrentLine[2];
IdleTime = $CurrentLine[3];
LogonTime = $CurrentLine[4..($CurrentLine.GetUpperBound(0))] -join ' '
}
# LogonTime = $CurrentLine[4..6] -join ' ';
}
else {
[pscustomobject]#{
UserName = $CurrentLine[0];
ComputerName = $Computer;
SessionName = $CurrentLine[1];
Id = $CurrentLine[2];
State = $CurrentLine[3];
IdleTime = $CurrentLine[4];
LogonTime = $CurrentLine[5..($CurrentLine.GetUpperBound(0))] -join ' '
}
}
}
}
catch {
New-Object -TypeName PSCustomObject -Property #{
ComputerName = $Computer
Error = $_.Exception.Message
} | Select-Object -Property UserName,ComputerName,SessionName,Id,State,IdleTime,LogonTime,Error
}
}
}
}

Related

powershell sort-object and keeps window open

I have following script to check the installed software on local and remote machines.
Usually it works fine but i have two problems. It only works when i open it in ISE. If i open it in a normal powershell, the script close immediately. Even a pause or read-host command won't work.
For example here is my script for a local machine. Hope you guys can help me.
Function Get-InstalledSoftware {
Param(
[Alias('Computer','ComputerName','HostName')]
[Parameter(
ValueFromPipeline=$True,
ValueFromPipelineByPropertyName=$true,
Position=1
)]
[string]$Name = $env:COMPUTERNAME
)
Begin{
$lmKeys = "Software\Microsoft\Windows\CurrentVersion\Uninstall","SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall"
$lmReg = [Microsoft.Win32.RegistryHive]::LocalMachine
$cuKeys = "Software\Microsoft\Windows\CurrentVersion\Uninstall"
$cuReg = [Microsoft.Win32.RegistryHive]::CurrentUser
}
Process{
if (!(Test-Connection -ComputerName $Name -count 1 -quiet)) {
Write-Error -Message "Unable to contact $Name. Please verify its network connectivity and try again." -Category ObjectNotFound -TargetObject $Computer
Break
}
$masterKeys = #()
$remoteCURegKey = [Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey($cuReg,$computer)
$remoteLMRegKey = [Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey($lmReg,$computer)
foreach ($key in $lmKeys) {
$regKey = $remoteLMRegKey.OpenSubkey($key)
foreach ($subName in $regKey.GetSubkeyNames()) {
foreach($sub in $regKey.OpenSubkey($subName)) {
$masterKeys += (New-Object PSObject -Property #{
"ComputerName" = $Name
"Name" = $sub.getvalue("displayname")
"SystemComponent" = $sub.getvalue("systemcomponent")
"ParentKeyName" = $sub.getvalue("parentkeyname")
"Version" = $sub.getvalue("DisplayVersion")
"UninstallCommand" = $sub.getvalue("UninstallString")
"InstallDate" = $sub.getvalue("InstallDate")
"RegPath" = $sub.ToString()
})
}
}
}
foreach ($key in $cuKeys) {
$regKey = $remoteCURegKey.OpenSubkey($key)
if ($regKey -ne $null) {
foreach ($subName in $regKey.getsubkeynames()) {
foreach ($sub in $regKey.opensubkey($subName)) {
$masterKeys += (New-Object PSObject -Property #{
"ComputerName" = $Name
"Name" = $sub.getvalue("displayname")
"SystemComponent" = $sub.getvalue("systemcomponent")
"ParentKeyName" = $sub.getvalue("parentkeyname")
"Version" = $sub.getvalue("DisplayVersion")
"UninstallCommand" = $sub.getvalue("UninstallString")
"InstallDate" = $sub.getvalue("InstallDate")
"RegPath" = $sub.ToString()
})
}
}
}
}
$woFilter = {$null -ne $_.name -AND $_.SystemComponent -ne "1" -AND $null -eq $_.ParentKeyName}
$props = 'Name','Version','ComputerName','Installdate','UninstallCommand','RegPath'
$masterKeys = ($masterKeys | Where-Object $woFilter | Select-Object $props | Sort-Object Name)
$masterKeys
}
End{}
}
Get-InstalledSoftware | select-object name | sort-object
Send your output somewhere, not just to the host window. I suspect the windows closes or your pause command is kicking in before its retrieved the results thus they aren't sent to the host window.
The following works fine for me run as a script:
Get-InstalledSoftware | Select-Object name | Sort-Object | Out-Gridview

How to display the variable result as table in email body or console screen?

The below script to get the logon users and send as email was working great but only on the console output only.
I am trying to get the result as a table so the result in the console and the email body will be like:
Server, ConnectionType, User, ID, State
PRDSVR16, rdp-tcp#44, SVC-SQL, 4, Active
PRDSVR10, rdp-tcp#27, Admin.domain, 6, Disc
SVR25-VM,console,domain.admin,8,Active
Open in new window
This is the script:
$Today = Get-Date -Format 'F'
$SessionList = "`n`nRDP Session List - " + $Today + "`n`n"
$CurrentSN = 0
# Get a list of servers from Active Directory
write-progress -activity "Getting list of servers from Active Directory" -status "... please wait ..."
$Servers = (Get-ADComputer -Filter { Enabled -eq $True -and OperatingSystem -like "*Server*" } -Properties OperatingSystem -SearchBase "OU=Data Center,DC=Company,DC=com") |
Where-Object { Test-Connection $_.Name -Count 1 -Quiet } |
Select-Object -ExpandProperty Name
$NumberOfServers = $Servers.Count
# Iterate through the retrieved list to check RDP sessions on each machine
ForEach ($Server in $Servers)
{
Write-Host "Processing $Server ..." -ForegroundColor Yellow
Write-progress -activity "Checking RDP Sessions" -status "Querying $Server" -percentcomplete (($CurrentSN / $NumberOfServers) * 100)
try
{
$SessionList += qwinsta /server:$Server |
Select-Object -Skip 1 |
% {
[PSCustomObject] #{
Type = $_.Substring(1, 18).Trim()
User = $_.Substring(19, 20).Trim()
ID = $_.Substring(41, 5).Trim()
State = $_.Substring(48, 6).Trim()
}
} |
? { $_.Type -notin 'console', 'services', 'rdp-tcp' -and $_.User -ne $null -and $_.User -ne 65536 } |
% {
"`n$Server logged in by $($_.User) on $($_.Type), session id $($_.ID) $($_.state)"
}
}
catch
{
$SessionList += "`n Unable to query " + $Server
write-host "Unable to query $Server! `n $($Error[0].Exception)" -foregroundcolor Red
}
$CurrentSN++
}
# Send the output the screen.
$SessionList + "`n`n"
$sendMailArgs = #{
From = "$env:USERNAME#$env:userdnsdomain"
To = 'SOC#domain.com'
SmtpServer = 'SMTP.domain.com'
Priority = 'High'
Body = $SessionList | Select-Object #{ N = 'Server'; E = { $Server } },
#{ N = 'User'; E = { $_.User } },
#{ N = 'LogonType'; E = { $_.Type } },
#{ N = 'ID'; E = { $_.ID } },
#{ N = 'State'; E = { $_.State } }
Subject = "$($SessionList.Count) Logged On users from $($NumberOfServers) online servers as at $($Today)"
}
Send-MailMessage #sendMailArgs
Rendering collected information in different places is way easier if you keep strict separation between data and presentation (or formatting) of said data.
For the $SessionList for example, that means doing less than what you're currently trying to do inside the loop:
$ErrorList = #()
$SessionList = foreach($server in $servers){
try{
qwinsta /server:$Server |Select-Object -Skip 1 |ForEach-Object {
[PSCustomObject] #{
Server = $server
Type = $_.Substring(1, 18).Trim()
User = $_.Substring(19, 20).Trim()
ID = $_.Substring(41, 5).Trim()
State = $_.Substring(48, 6).Trim()
}
} |Where-Object { $_.Type -notin 'console', 'services', 'rdp-tcp' -and $_.User -ne $null -and $_.User -ne 65536 }
}
catch{
$ErrorList += [pscustomobject]#{
Server = $server
ErrorRecord = $_
}
}
}
Notice how I don't construct any strings - I just create the custom objects, filter them - and then leave them as-is.
Now it becomes much easier to format the data as desired for different output media:
# For console output, simply pipe to Format-Table
$SessionList |Format-Table
if($ErrorList.Count -gt 0){
Write-Warning "The following servers had errors, please inspect"
$ErrorList |Format-Table
}
# For email output we can use `ConvertTo-Html`
$Body = $SessionList |ConvertTo-Html -As Table -Fragment
if($ErrorList.Count -gt 0){
$ErrorTable = $ErrorList |ConvertTo-Html -As Table -Fragment
$Body = $Body,$ErrorTable -join '<br />'
}
$sendMailArgs = #{
# ...
Body = ConvertTo-Html -Body $Body -Title "Session list"
BodyAsHtml = $true
# ...
}

Log off users remotely

found this script to log off a single username
$scriptBlock = {
$ErrorActionPreference = 'Stop'
try {
## Find all sessions matching the specified username
$sessions = quser | Where-Object {$_ -match 'username'}
## Parse the session IDs from the output
#foreach($sessionsUser in $sessions){
$sessionIds = ($sessions -split ' +')[2]
Write-Host "Found $(#($sessionIds).Count) user login(s) on computer."
## Loop through each session ID and pass each to the logoff command
$sessionIds | ForEach-Object {
Write-Host "Logging off session id [$($_)]..."
logoff $_
}
#}
} catch {
if ($_.Exception.Message -match 'No user exists') {
Write-Host "The user is not logged in."
} else {
throw $_.Exception.Message
}
}
}
## Run the scriptblock's code on the remote computer
Invoke-Command -ComputerName NAME -ScriptBlock $scriptBlock
Is it possible to do the same for all users logged in session ?
I know that -match return first parameter , i tried to do " -ne $Null" but it returns a whole column with sessions instead a row and only check row [0] and the ones with actuall parameters ...
Iterate through the collection and logoff all the Id present:
$ScriptBlock = {
$Sessions = quser /server:$Computer 2>&1 | Select-Object -Skip 1 | ForEach-Object {
$CurrentLine = $_.Trim() -Replace '\s+',' ' -Split '\s'
# If session is disconnected different fields will be selected
If ($CurrentLine[2] -eq 'Disc') {
[pscustomobject]#{
UserName = $CurrentLine[0];
Id = $CurrentLine[1]
}
}
Else {
[pscustomobject]#{
UserName = $CurrentLine[0];
Id = $CurrentLine[2]
}
}
}
$Sessions | ForEach-Object {
logoff $_.Id
}
}
Invoke-Command -ComputerName gmwin10test -ScriptBlock $ScriptBlock
Instead of
$sessions = quser | Where-Object {$_ -match 'username'}
## Parse the session IDs from the output
#foreach($sessionsUser in $sessions){
$sessionIds = ($sessions -split ' +')[2]
use:
$sessionIDs = #()
$sessions = (quser) -split '`r`n'
for ($i=1;$i -lt $sessions.length;$i++){
$sessionIDs += $sessions[$i].substring(42,4).trim()
}
This way all sessions are recorded and the IDs are added to the $sessionIDs-array.
I'm using substring since the regex is not working if the session is disconnected. Also using forinstead of foreach since the first entry is the header ("ID").

PowerShell Workflow scoping issue

Recently we started exploring Workflows in PowerShell. It greatly enhances the execution speed, but it adds an extra level of complexity too.
The code below has a scoping issue. The variables in the Parallel clause ($Workflow:Ports and $Workflow:Drivers) ar apparently shared over the different $ComputerNames instead of being specific to one $ConputerName. When checking technet I can't seem to figure out how to make the variables $Ports and $Drivers specific to that computer ($C).
When using $Workflow:Ports they are shared between all computers, and this is not what we want. When using $Ports it's not available in the InlineScript clause.
The code:
Workflow Get-PrintersInstalledHC {
Param (
[String[]]$ComputerName
)
Foreach -Parallel ($S in $ComputerName) {
$Computer = InlineScript {
[PSCustomObject]#{
ComputerName = $Using:S
ComputerStatus = $null
Printers = $null
RetrievalDate = Get-Date
}
}
# $VerbosePreference = [System.Management.Automation.ActionPreference]$Using:VerbosePreference
Try {
$Printers = Get-Printer -ComputerName $S -Full -EA Stop
if ($Printers) {
Write-Verbose "$S Found $($Printers.Count) printers"
Parallel {
$Workflow:Ports = Get-PrinterPort -ComputerName $S
$Workflow:Drivers = Get-PrinterDriver -ComputerName $S
}
$CimConfig = InlineScript {
Try {
#region CmdLets that require admin permissions
$Params = #{
ComputerName = $Using:S
ClassName = 'Win32_PrinterConfiguration'
Property = '*'
ErrorAction = 'Stop'
Verbose = $false
}
$Config = Get-CimInstance #Params
Foreach ($P in $Using:Printers) {
Foreach($C in $Config) {
if ($P.Name -eq $C.Name) {
#{
PrinterName = $P.Name
CimStatus = 'Ok'
DriverVersionCim = $C.DriverVersion
Collate = $C.Collate
Color = $C.Color
Copies = $C.Copies
Duplex = $C.Duplex
PaperSize = $C.PaperSize
Orientation = $C.Orientation
PrintQuality = $C.PrintQuality
MediaType = $C.MediaType
DitherType = $C.DitherType
}
Break
}
}
}
#endregion
}
Catch {
Foreach ($P in $Using:Printers) {
#{
PrinterName = $P.Name
CimStatus = 'No admin permissions'
}
}
}
}
Foreach -parallel ($P in $Printers) {
$PrinterConfig = InlineScript {
$P = $Using:P
$Port = $Using:Ports | Where {$_.Name -eq $P.PortName}
$Driver = $Using:Drivers | Where {$_.Name -eq $P.DriverName}
Write-Verbose "$Using:S Printer '$($P.Name)'"
$DriverManufacturer = if ($Driver.Manufacturer) {$Driver.Manufacturer} else {
if ($Driver.Name -like '*Microsoft*') {'Microsoft'}
}
$DriverVersion = if ($Driver.DriverVersion -eq '0') {$null} else {
$Driver.DriverVersion
}
#{
Online_Hostname = if ($P.Name) {Test-Connection $P.Name -Quiet -EA Ignore} else {$null}
Online_PortHostAddress = if ($Port.PrinterHostAddress) {Test-Connection $Port.PrinterHostAddress -Quiet -EA Ignore} else {$null}
PortHostAddress = if ($Port.PrinterHostAddress) {$Port.PrinterHostAddress} else {$null}
PortDescription = if ($Port.Description) {$Port.Description} else {$null}
DriverType = $Driver.PrinterEnvironment -join ','
DriverManufacturer = ($DriverManufacturer | Select -Unique) -join ','
DriverVersion = ($DriverVersion | Select -Unique) -join ','
}
}
$Cim = $CimConfig | Where-Object -FilterScript {$P.Name -eq $_.PrinterName}
$P | Add-Member -NotePropertyMembers ($PrinterConfig + $Cim) -TypeName NoteProperty
}
InlineScript {
$Computer = $Using:Computer
$Computer.Printers = $Using:Printers
$Computer.ComputerStatus = 'Ok'
$Computer
}
}
else {
Write-Verbose "$S No printers found"
}
}
Catch {
InlineScript {
$Computer = $Using:Computer
if (Test-Connection $Using:S -Count 2 -Quiet) {
$Computer.ComputerStatus = $Using:_.Message
}
else {
$Computer.ComputerStatus = 'Offline'
}
$Computer
}
}
}
}

Is there any script to get logged in user info via email

Hi guys I am wondering if there is any script that will notify with email about logged in user information like username, computer name, client ip, client name, date and time etc.
Here is the script i get from technet but it displays computer name as local host, can it output a logged file and email as attachment?
Param(
[CmdletBinding()]
[Parameter(ValueFromPipeline=$true,
ValueFromPipelineByPropertyName=$true)]
[string[]]$ComputerName = 'localhost'
)
Begin {
$ErrorActionPreference = 'Stop'
}
Process {
foreach ($Computer in $ComputerName) {
try {
quser /server:$Computer 2>&1 | Select-Object -Skip 1 | ForEach-Object {
$CurrentLine = $_.Trim() -Replace '\s+',' ' -Split '\s'
$HashProps = #{
UserName = $CurrentLine[0]
ComputerName = $Computer
}
# If session is disconnected different fields will be selected
if ($CurrentLine[2] -eq 'Disc') {
$HashProps.SessionName = $null
$HashProps.Id = $CurrentLine[1]
$HashProps.State = $CurrentLine[2]
$HashProps.IdleTime = $CurrentLine[3]
$HashProps.LogonTime = $CurrentLine[4..6] -join ' '
$HashProps.LogonTime = $CurrentLine[4..($CurrentLine.GetUpperBound(0))] -join ' '
} else {
$HashProps.SessionName = $CurrentLine[1]
$HashProps.Id = $CurrentLine[2]
$HashProps.State = $CurrentLine[3]
$HashProps.IdleTime = $CurrentLine[4]
$HashProps.LogonTime = $CurrentLine[5..($CurrentLine.GetUpperBound(0))] -join ' '
}
New-Object -TypeName PSCustomObject -Property $HashProps |
Select-Object -Property UserName,ComputerName,SessionName,Id,State,IdleTime,LogonTime,Error
}
} catch {
New-Object -TypeName PSCustomObject -Property #{
ComputerName = $Computer
Error = $_.Exception.Message
} | Select-Object -Property UserName,ComputerName,SessionName,Id,State,IdleTime,LogonTime,Error
}
}
}
I guess script usage should be:
save it in a .ps1 file
call it with a -ComputerName 'hostname' parameter, allowing you to choose the targeted computer
Here, 'localhost' is the default value for this parameter.
To send an email, check the Send-MailMessage cmdlet (PS 3.0 required).
If you need to run a script as a scheduled task, set the Action fields like this:
program = C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe
arguments = -File "C:\path\to\script.ps1"