Retrieve User name with Get-WinEvent - powershell

I am trying to retrieve a list of User names from Event Viewer log data. The only selector that I saw available was UserID. I would like to convert that to what I see under General > User instead of a SID. I am a PowerShell beginner so I apologize in advance.
This is the script I am using right now. It works great but I would like to add a User name to the columns.
#Server list
$VDI = Get-Content "C:\Scripts\IM\AvayaList.csv"
#Query remote machines
Invoke-Command $VDI {
$Filter = #{
Logname = 'Citrix-VDA-CQI/Admin'
Level = 1,2,3
StartTime = [datetime]::Today.AddDays(-1)
}
Get-WinEvent -FilterHashtable $Filter
} | Select-Object MachineName,TimeCreated,Level,ID,Message | Out-GridView -Title "Results"
I saw this on another post but I am having a hard time integrating what I have with what I found.
Get-WinEvent -MaxEvents 1000 | foreach {
$sid = $_.userid;
if($sid -eq $null) { return; }
$objSID = New-Object System.Security.Principal.SecurityIdentifier($sid);
$objUser = $objSID.Translate([System.Security.Principal.NTAccount]);
Write-Host $objUser.Value;
}
I am using PowerShell v5.1.14393.3866
Thank you!

You could just write a little helper function to resolve the SIDs. Also, just as you used a variable for your filter hashtable, you can use a variable to store the desired properties to make the code easier to read.
Function Resolve-SID ($sid)
{
if($sid)
{
$objSID = New-Object System.Security.Principal.SecurityIdentifier($sid)
$objSID.Translate([System.Security.Principal.NTAccount]).value
}
else
{
'N/A'
}
}
$selectprops = "MachineName",
"TimeCreated",
"Level",
"ID",
"Message",
#{n="User";e={Resolve-SID $_.UserID}}
#Server list
$VDI = Get-Content "C:\Scripts\IM\AvayaList.csv"
#Query remote machines
Invoke-Command $VDI {
$Filter = #{
Logname = 'Citrix-VDA-CQI/Admin'
Level = 1,2,3
StartTime = [datetime]::Today.AddDays(-1)
}
Get-WinEvent -FilterHashtable $Filter
} | Select-Object $selectprops | Out-GridView -Title "Results"

Related

Powershell return variable from within a Invoke-Command

I'm developing a powershell script (and kind of new to it) to go to a couple of servers and extract the RDP logons, so we can check if a certain policy is being followed.
So I've search a bit and now I got the script output exatcly as I want. But now I want to send the result over email.
But I have a problem, because the variable which is output to console (with the info I need) is inside a Invoke-Command, so I cannot use it after outside the Invoke-Command to send the email.
This is my code:
$ServersToCheck = Get-Content "C:\Temp\Servers-RDP2.txt"
foreach ($server in $ServersToCheck) {
Invoke-Command -ComputerName $server -ErrorAction SilentlyContinue {
$username = "user"
$FilterPath = "<QueryList><Query Id='0'><Select Path='Microsoft-Windows-TerminalServices-RemoteConnectionManager/Operational'>*[System[(EventID=1149) and TimeCreated[timediff(#SystemTime) <= 604800000]]] and *[UserData[EventXML[#xmlns='Event_NS'][Param1='{0}']]]</Select></Query></QueryList>" -f $username
$RDPAuths = Get-WinEvent -ErrorAction SilentlyContinue -LogName 'Microsoft-Windows-TerminalServices-RemoteConnectionManager/Operational' -FilterXPath $FilterPath
[xml[]]$xml = $RDPAuths | Foreach { $_.ToXml() }
$EventData = Foreach ($event in $xml.Event) {
New-Object PSObject -Property #{
TimeCreated = (Get-Date ($event.System.TimeCreated.SystemTime) -Format 'dd-MM-yyyy hh:mm:ss')
User = $event.UserData.EventXML.Param1
Domain = $event.UserData.EventXML.Param2
Client = $event.UserData.EventXML.Param3
Server = hostname
}
}
$EventData | FT
}
}
So, I need to use $EventData outside the Invoke-Command so I can add the results of all servers and then send it over by email.
How can I use that variable outside the Invoke-Command?
Thanks

Powershell script RDP connection data

I am trying to get the source network address for an RDP connection to send an email when a user connects to the server. I have everything but the source address. My script is triggered by event 1149 in the RemoteConnectionManager Operational log. I only need to access either the event data or source address from the system.
$SmtpClient = new-object system.net.mail.smtpClient
$MailMessage = New-Object system.net.mail.mailmessage
$SmtpClient.Host = "mail.scomage.com"
$mailmessage.from = ("BWAQBW#BWAServer.com")
$mailmessage.To.add("support#scomage.com")
$mailmessage.Subject = “BWAQB RDP”
$mailmessage.Body = [System.Security.Principal.WindowsIdentity]::GetCurrent().Name + “ logged into BWA QB Server”
$smtpclient.Send($mailmessage)
Final script after excellent help by Steven:
$LastEvent = Get-WinEvent -FilterHashtable `
#{ LogName = "Microsoft-Windows-TerminalServices-
RemoteConnectionManager/Operational"; Id = 1149} |
Select-Object *,
#{ Name = 'UserName'; Expression = `
{ if ($_.Properties[0].Value) { $_.Properties[0].Value } else { "Administrator" } } },
#{ Name = 'SourceIP'; Expression = { $_.Properties[2].Value } } |
Sort-Object TimeCreated |
Select-Object -Last 1
$MailParams =
#{
SmtpServer = "mail.scomage.com"
From = "BWAQBW#BWAServer.com"
To = "support#scomage.com"
Subject = "BWAQB RDP " + $LastEvent.UserName + " " + $LastEvent.SourceIP
Body = $LastEvent.UserName + " logged into BWA QB Server at " + `
$LastEvent.TimeCreated.ToString('g') + " From: " + $LastEvent.SourceIP
}
Send-MailMessage #MailParams
Script to send logon email:
$Event = Get-WinEvent -FilterHashTable #{
LogName='Security'; ID=4624; StartTime=$DateLess10; } |
Where-Object{$_.Properties[8].Value -eq 10 } |
Sort-Object TimeCreated |
Select-Object -Last 1
if( $Event.Properties[8].Value -eq 10 )
{
$MailParams =
#{
SmtpServer = "mail.scomage.com"
From = "BWAQBW#BWAServer.com"
To = "support#scomage.com"
Subject = "BWAQB Remote Login " + $Event.Properties[5].Value + " " + $Event.Properties[18].Value
Body = $Event.Properties[5].Value + " logged into BWA QB Server at " + `
$Event.TimeCreated.ToString('g') + " From: " + $Event.Properties[18].Value
}
Send-MailMessage #MailParams
}
Updated Based on Comments
Looks like the source address for the connection is recorded as the 3rd property in event 1149. Properties are an object array of type [System.Diagnostics.Eventing.Reader.EventProperty[]] which just means you have to access the sub-property .Value .
An example to get the last event:
$LastEvent =
Get-WinEvent -FilterHashtable #{ LogName = "Microsoft-Windows-TerminalServices-RemoteConnectionManager/Operational"; Id = 1149} |
Select-Object *,
#{ Name = 'UserName'; Expression = { $_.Properties[0].Value } },
#{ Name = 'SourceIP'; Expression = { $_.Properties[2].Value } } |
Sort-Object TimeCreated |
Select-object -Last 1
Since I'm not sure Get-WinEvent will guarantee chronological order the the Sort-Object cmdlet is used. So it should result in the the last 1149 event. However, it doesn't guarantee it will be for the user that was returned by System.Security.Principal.WindowsIdentity]::GetCurrent().Name. I don't know what kind of machine this is or under what circumstances the script will be run. For example if it's a multi-user system (implied by Terminal Server) I suspect there's an edge case where last event may not be for the current user.
So, I'm guessing that we should isolate the last event for that user. We can do that with one of these samples:
Option 1:
$NTUserName = [System.Security.Principal.WindowsIdentity]::GetCurrent().Name
$UserName = $NTUserName.Split('\')[-1]
$Events =
Get-WinEvent -FilterHashtable #{ LogName = "Microsoft-Windows-TerminalServices-RemoteConnectionManager/Operational"; Id = 1149} |
Select-Object *,
#{ Name = 'UserName'; Expression = { $_.Properties[0].Value } },
#{ Name = 'SourceIP'; Expression = { $_.Properties[2].Value } } |
Group-Object -Property UserName -AsHashTable -AsString
$LastEvent = ($Events[$UserName] | Sort-Object TimeGenerated)[-1]
Option 2:
$NTUserName = [System.Security.Principal.WindowsIdentity]::GetCurrent().Name
$UserName = $NTUserName.Split('\')[-1]
$LastEvent =
Get-WinEvent -FilterHashtable #{ LogName = "Microsoft-Windows-TerminalServices-RemoteConnectionManager/Operational"; Id = 1149} |
Select-Object *,
#{ Name = 'UserName'; Expression = { $_.Properties[0].Value } },
#{ Name = 'SourceIP'; Expression = { $_.Properties[2].Value } } |
Where-Object{ $_.UserName -eq $UserName } |
Sort-Object TimeCreated |
Select-Object -Last 1
In both cases $LastEvent should have the last 1149 event for the current user. The first option has less sorting, because it is only sorting events for the specific user. you can use $LastEvent.SourceIP for the email.
A secondary note: You don't need to use System.Net.Mail.SMTPClient. you can use Send-MailMessage combined with command splatting that might look like:
$MailParams =
#{
SmtpServer = "mail.scomage.com"
From = "BWAQBW#BWAServer.com"
To = "support#scomage.com"
Subject = "BWAQB RDP"
Body = $UserName + " logged into BWA QB Server at " + $LastEvent.TimeCreated.ToString('g') + " From: " + $LastEvent.SourceIP
}
Send-MailMessage #MailParams
Note: Above was also updated after your comments to take advantage of the newer revisions. Note: .ToString('g') results in date format like '6/30/2020 9:17 PM'
I'm not sure if the body or subject is exactly right, but you can work that out.

Windows "net group /domain" output filter

I need to grab members in particular AD group and add them into array. Using net group I can easily get the members of AD group.
However, I am not familier with the filter on Windows. I just want to get the user name from output.
Group name test
Comment
Members
---------------------------------------------------------------------
mike tom jackie
rick jason nick
The command completed successfully.
I can't use Get-ADGroupMember command using PowerShell. If there is a way to get a data and filter using PowerShell, it is also OK.
Well, the good news is that there is rarely only one way to do things in PowerShell. Here's part of a larger script I have on hand for some group related things where I don't always have the AD module available (such as on servers that other teams own):
$Identity = 'test'
$LDAP = "dc="+$env:USERDNSDOMAIN.Replace('.',',dc=')
$Filter = "(&(sAMAccountName=$Identity)(objectClass=group))"
$Searcher = [adsisearcher]$Filter
$Searcher.SearchRoot = "LDAP://$LDAP"
'Member','Description','groupType' | %{$Searcher.PropertiesToLoad.Add($_)|Out-Null}
$Results=$Searcher.FindAll()
$GroupTypeDef = #{
1='System'
2='Global'
4='Domain Local'
8='Universal'
16='APP_BASIC'
32='APP_QUERY'
-2147483648='Security'
}
If($Results.Count -gt 0){
$Group = New-Object PSObject #{
'DistinguishedName'=[string]$Results.Properties.Item('adspath') -replace "LDAP\:\/\/"
'Scope'=$GroupTypeDef.Keys|?{$_ -band ($($Results.properties.item('GroupType')))}|%{$GroupTypeDef.get_item($_)}
'Description'=[string]$Results.Properties.Item('description')
'Members'=[string[]]$Results.Properties.Item('member')|% -Begin {$Searcher.PropertiesToLoad.Clear();$Searcher.PropertiesToLoad.Add('objectClass')|Out-Null} {$Searcher.Filter = "(distinguishedName=$_)";[PSCustomObject][ordered]#{'MemberType'=$Searcher.FindAll().Properties.Item('objectClass').ToUpper()[-1];'DistinguishedName'=$_}}
}
$Group|Select DistinguishedName,Scope,Description
$Group.Members|FT -AutoSize
}
Else{"Unable to find group '$Group' in '$env:USERDNSDOMAIN'.`nPlease check that you can access that domain from your current domain, and that the group exists."}
Here's one way to get the direct members of an AD group without using the AD cmdlets:
param(
[Parameter(Mandatory)]
$GroupName
)
$ADS_ESCAPEDMODE_ON = 2
$ADS_SETTYPE_DN = 4
$ADS_FORMAT_X500 = 5
function Invoke-Method {
param(
[__ComObject]
$object,
[String]
$method,
$parameters
)
$output = $object.GetType().InvokeMember($method,"InvokeMethod",$null,$object,$parameters)
if ( $output ) { $output }
}
function Set-Property {
param(
[__ComObject]
$object,
[String]
$property,
$parameters
)
[Void] $object.GetType().InvokeMember($property,"SetProperty",$null,$object,$parameters)
}
$Pathname = New-Object -ComObject "Pathname"
Set-Property $Pathname "EscapedMode" $ADS_ESCAPEDMODE_ON
$Searcher = [ADSISearcher] "(&(objectClass=group)(name=$GroupName))"
$Searcher.PropertiesToLoad.AddRange(#("distinguishedName"))
$SearchResult = $searcher.FindOne()
if ( $SearchResult ) {
$GroupDN = $searchResult.Properties["distinguishedname"][0]
Invoke-Method $Pathname "Set" #($GroupDN,$ADS_SETTYPE_DN)
$Path = Invoke-Method $Pathname "Retrieve" $ADS_FORMAT_X500
$Group = [ADSI] $path
foreach ( $MemberDN in $Group.member ) {
Invoke-Method $Pathname "Set" #($MemberDN,$ADS_SETTYPE_DN)
$Path = Invoke-Method $Pathname "Retrieve" $ADS_FORMAT_X500
$Member = [ADSI] $Path
"" | Select-Object `
#{
Name="group_name"
Expression={$Group.name[0]}
},
#{
Name="member_objectClass"
Expression={$member.ObjectClass[$Member.ObjectClass.Count - 1]}
},
#{
Name="member_sAMAccountName";
Expression={$Member.sAMAccountName[0]}
}
}
}
else {
throw "Group not found"
}
This version uses the Pathname COM object to handle name escaping and outputs the the object class and sAMAccountName for each member of the group.

run and handle output from ps1 script

The goal : get all logged in / logged out users from the system.
Those users who logged in / logged out by using remote desktop connection.
My script :
Param(
[array]$ServersToQuery = (hostname),
[datetime]$StartTime = "January 1, 1970"
)
foreach ($Server in $ServersToQuery) {
$LogFilter = #{
LogName = 'Microsoft-Windows-TerminalServices-LocalSessionManager/Operational'
ID = 21, 23, 24, 25
StartTime = $StartTime
}
$AllEntries = Get-WinEvent -FilterHashtable $LogFilter -ComputerName $Server
$AllEntries | Foreach {
$entry = [xml]$_.ToXml()
[array]$Output += New-Object PSObject -Property #{
TimeCreated = $_.TimeCreated
User = $entry.Event.UserData.EventXML.User
IPAddress = $entry.Event.UserData.EventXML.Address
EventID = $entry.Event.System.EventID
ServerName = $Server
}
}
}
$FilteredOutput += $Output | Select TimeCreated, User, ServerName, IPAddress, #{Name='Action';Expression={
if ($_.EventID -eq '21'){"logon"}
if ($_.EventID -eq '22'){"Shell start"}
if ($_.EventID -eq '23'){"logoff"}
if ($_.EventID -eq '24'){"disconnected"}
if ($_.EventID -eq '25'){"reconnection"}
}
}
$Date = (Get-Date -Format s) -replace ":", "."
$FilePath = "$env:USERPROFILE\Desktop\$Date`_RDP_Report.csv"
$FilteredOutput | Sort TimeCreated | Export-Csv $FilePath -NoTypeInformation
Write-host "Writing File: $FilePath" -ForegroundColor Cyan
Write-host "Done!" -ForegroundColor Cyan
#End
I really do not understand ps1 scripts. I've found this script but i want to use it for my purposes.
When i try to execute it with c# :
Scenario 1 :
string scriptText = "C:\\MyPath\\script.ps1";
try
{
Runspace runspace = RunspaceFactory.CreateRunspace();
// open it
runspace.Open();
// create a pipeline and feed it the script text
Pipeline pipeline = runspace.CreatePipeline();
pipeline.Commands.AddScript(scriptText);
Collection<PSObject> results = pipeline.Invoke();
It throws an error :
ps1 is not digitally signed.
Second scenario :
using (PowerShell PowerShellInstance = PowerShell.Create())
{
PowerShellInstance.AddScript(str_Path);
Collection<PSObject> PSOutput = PowerShellInstance.Invoke();
if (PowerShellInstance.Streams.Error.Count > 0)
{
// error records were written to the error stream.
// do something with the items found.
}
}
Streams are always empty. Actually it always count 0 rows.
Any idea/suggestion how to get this done ?
I used Write-Output instead of Write-Host into the end of the script without generating csv file. Credits to this.

Unable to catch error in PowerShell script, while using quest active roles to query active directory

I have a script which returns the location of a list of computers within AD. This works fine for my current domain and if I specify "-searchroot" it works for the others.
If I query a computer not on the correct domain, with 'get-qadcomputer' it does not return any information to the console, so need a method to catch and try alternatives?
I have tried this, which just works for all computer in DC=Domain1 (my current domain):
Add-PSSnapin Quest.ActiveRoles.ADManagement
$strPath = "C:\sample.xlsx"
$objExcel = new-object -ComObject Excel.Application
$objExcel.Visible = $false
$WorkBook = $objExcel.Workbooks.Open($strPath)
$worksheetIn = $workbook.sheets.item("Asset")
$worksheetOut = $workbook.sheets.item("Asset")
$intRowMax = ($worksheetIn.UsedRange.Rows).count
$Columnnumber = 1
For($intRow = 2 ; $intRow -le $intRowMax ; $intRow++) {
Try
{
$name = $worksheetIn.cells.item($intRow,$ColumnNumber).value2
"Querying $name...$introw of $intRowMax"
$OU = Get-QADcomputer -searchroot 'OU=Workstations,DC=DOMAIN1'-LdapFilter "(CN=$Name)"`
| ft Location -HideTableHeaders
$Out = Format-list -InputObject $OU | out-string
$worksheetOut.cells.item($intRow,6) = "$Out"
}
Catch
{
$name = $worksheetIn.cells.item($intRow,$ColumnNumber)
"Querying $name...$introw of $intRowMax"
$OU = Get-QADComputer -SearchRoot 'OU=Workstations,DC=DOMAIN2' -LdapFilter (CN=$Name)"`
| ft Location -HideTableHeaders
$Out = Format-list -InputObject $OU | out-string
$worksheetOut.cells.item($intRow,6) = "$Out"
}
}
$objexcel.save()
$objExcel.workbooks.close()
$objexcel.application.quit()
[System.Runtime.Interopservices.Marshal]::ReleaseComObject($Workbook)
[System.Runtime.Interopservices.Marshal]::ReleaseComObject($objExcel)
Remove-Variable objExcel
You could check if $OU is equal to $null. I suspect it just isn't finding anything if you get no error text or it doesn't take you to the catch handler. Another option is to check $? right after the line calling Get-QADComputer but I suspect you will get True (command succeeded). But if you get False, then that tells you it didn't succeed.