I want to take a domain user and for it want to check the Security Event Logs for say Logon and then Print the events which match but it returns me null value:
Get-EventLog -Log Security -Computer PC1 -InstanceID 4624 -After(Get-Date).AddDays(-2) | ? {
$_.Message -match "Account Name:\s+qasimali\s" -and
$_.Message -match 'Logon Type:\s+(2|10)\s"
}
but it generates no data for output
Read-Host : name cannot be Null or Empty.
Whereas command runs and gives no error. I just want to check whether this command is running fine or not.
The way I have done this in the past is as follows ( Thoroughly Commented for clarity) :
## Set Username Input
$UserInput = "DOMAINUSER"
## Set date in past to retrieve events up to
$StartTime = ((Get-Date).AddMinutes(-2))
##Set Domain Controller to search on
$ComputerName = "DC1"
## Retrieve Event 4624 from DC Eveng Logs
$Logons = Get-WinEvent -ComputerName $ComputerName -FilterHashTable #{LogName="Security"; ID="4624"; StartTime=$StartTime;EndTime=(Get-Date)}
## Initialize variable to store outputs in
$EventOutput = #()
## Enumerate Events to retrieve usernames to compare against User Input
foreach ($Logon in $Logons) {
## Convert Event to XML
$LogonXML = [XML]$Logon.ToXML()
## Retrieve Username from XML Object
$LogonUser = (($LogonXML.Event.EventData.Data | Select "#text")[5])."#text"
## Retrieve Logon Type from XML Object
$LogonType = (($LogonXML.Event.EventData.Data | Select "#text")[8])."#text"
## Check Event Username matches User Input
if ($LogonUser -match $UserInput) {
## Check LogonType is correct
if ($LogonType -eq 2 -or $LogonType -eq 10) {
## Append Event Object to Event Output
$EventOutput += $Logon
}
}
}
## Output Resulting Event Output Object
$EventOutput
The Resulting Output can be manipulated to retrieve whatever details you wish. I find converting each Object to XML to parse further values useful.
NOTE : I've just thrown this together quickly from memory, this can be quickly restructured to enable other queries if required. Start and End Times will need to be changed to extract information from the correct timespan.
Related
I'm trying to extract all usernames that has failed login atempts from Event Viewer log and then list only the usernames. However the data for each entry is text so I have a hard time extracting only the names (Intruder123 in this case). It would be a couple of hundred account names stored in an array.
$String = Get-WinEvent #{LogName='Security';ProviderName='Microsoft-Windows-Security-Auditing';ID=4625 } -ComputerName SECRETSERVER |
Select-Object -ExpandProperty Message
$string -match "Account Name: (?<content>.*)"
$matches['content']
The data looks like this (multiple times):
Account For Which Logon Failed:
Security ID: S-1-0-0
Account Name: Intruder123
Account Domain: SECRET.LOCAL
I think you could collect some more information like the time the failed logon happened and on which computer. For that, create a resulting array of objects.
Also, trying to parse the Message property can be cumbersome and I think it is much better to get the info from the Event as XML:
$filter = #{LogName='Security';ProviderName='Microsoft-Windows-Security-Auditing';ID=4625 }
$result = Get-WinEvent -FilterHashtable $filter -ComputerName SECRETSERVER | ForEach-Object {
# convert the event to XML and grab the Event node
$eventXml = ([xml]$_.ToXml()).Event
$userName = ($eventXml.EventData.Data | Where-Object { $_.Name -eq 'TargetUserName' }).'#text'
$computer = ($eventXml.EventData.Data | Where-Object { $_.Name -eq 'WorkstationName' }).'#text'
# output the properties you need
[PSCustomObject]#{
Time = [DateTime]$eventXml.System.TimeCreated.SystemTime
UserName = $userName
Computer = $computer
}
}
# output on screen
$result
# output to CSV file
$result | Export-Csv -Path 'X:\FailedLogons.csv' -NoTypeInformation
I'm trying to install/activate a MAK key on remote servers. All of them have RemotePS enabled and firewall exception rules in place.
$Results = Invoke-Command -ComputerName Server1 {
$Props = #{ComputerName = $env:ComputerName}
slmgr.vbs /ipk "12345-12345-12345-12345-12345"
$LicStatus = slmgr.vbs /dlv
$Props.Add('LicenseStatus',$LicStatus)
New-Object -TypeName PSObject -Property $Props
}
$Results | Select-Object ComputerName,LicenseStatus
The above does install the MAK key but I don't get any confirmation of this process which is why I've tried adding in the license check option (/dlv) but get nothing returned in the LicenseStatus field. I'm assuming this is because it returns a multi-value maybe!?
Ultimately I'm just trying to get confirmation that the key was installed. There are articles out there about performing this using RemotePS but they all say a notification message is returned for each computer which isn't the case in my experience: https://4sysops.com/archives/change-a-product-key-remotely-with-powershell/
Any ideas how I can check this?
I would call the slmgr.vbs script using Cscript.exe in order to get the results as string array. Otherwise the system will default to using Wscript.exe which is designed to output everything in a messagebox.
Unfortunately, all output of slmgr is localized, so using a regex or something on the LicenseStatus is a no go (on a Dutch NL machine it reads 'Licentiestatus')
What you can do is using switch /dli, because that returns a string array where the last (not empty) value has the status.
Try
$Results = Invoke-Command -ComputerName Server1 {
# install MAK key
$null = cscript.exe "$env:SystemRoot\System32\slmgr.vbs" /ipk "12345-12345-12345-12345-12345"
# test LicenseStatus
$LicStatus = (((cscript.exe "$env:SystemRoot\System32\slmgr.vbs" /dli) |
Where-Object { $_ -match '\S' })[-1] -split ':', 2)[1].Trim()
# return an object
[PsCustomObject]#{
ComputerName = $env:COMPUTERNAME
LicenseStatus = $LicStatus
}
}
$Results
Does anyone know or have a script which tells you the actual device locking out an AD account. I have a working script which lists all users locked out in the last 3 days which tells me the DC its locked out. Rather than having to connect to this or via event log and locate the event id, i wanted to know if there was a PS script out there which would output where. Then we can go to said device and fix.
Google has brought up a few suggestions but not the clearest and some just do what i can already get via the current script.
Thanks
This returns an array of PsObjects, where:
property TargetUserName holds the user SamAccountName that is locked out
property TargetDomainName contains the computer name where the lockout originated from
property EventDate will show the time and date the lockout occurred
Code:
# get the domain controller that has the PDC Emulator Role
$pdc = (Get-ADDomain).PDCEmulator
$splat = #{
FilterHashtable = #{LogName="Security";Id=4740}
MaxEvents = 100
ComputerName = $pdc
Credential = Get-Credential -Message "Please enter credentials for '$pdc'"
}
$lockedOut = Get-WinEvent #splat | ForEach-Object {
# convert the event to XML and grab the Event node
$eventXml = ([xml]$_.ToXml()).Event
# create an ordered hashtable object to collect all data
# add some information from the xml 'System' node first
$evt = [ordered]#{
EventDate = [DateTime]$eventXml.System.TimeCreated.SystemTime
Level = [System.Diagnostics.Tracing.EventLevel]$eventXml.System.Level
}
# next see if there are childnodes under 'EventData'
if ($eventXml.EventData.HasChildNodes) {
$eventXml.EventData.ChildNodes | ForEach-Object {
$name = if ($_.HasAttribute("Name")) { $_.Name } else { $_.LocalName }
$value = $_.'#text'
if ($evt[$name]) {
# if an item with that name already exists, make it an array and append
$evt[$name] = #($evt[$name]) + $value
}
else { $evt[$name] = $value }
}
}
# output as PsCustomObject. This ensures the $result array can be written to CSV easily
[PsCustomObject]$evt
}
# output on screen
$lockedOut | fl *
# output to csv file
$lockedOut | Export-Csv -Path 'D:\lockedout.csv' -NoTypeInformation
If you want to search for a specific user (SamAccountName) for instance, just do
$lockedOut | Where-Object { $_.TargetUserName -eq 'UserSamAccountName' }
Hope that helps
Currently, I'm trying to add a function to my powershell script with the following goal:
On a computer that isn't added to the domain (yet), have it search a local AD server (Not azure) for the next available name based off the user's input.
I have tried and failed to use arrays in the past, and I want to use the Get-ADComputer cmdlet in this, but I'm not sure how to implement it.
$usrinput = Read-Host 'The current PC name is $pcname , would you like to rename it? (Y/N)'
if($usrinput -like "*Y*") {
Write-Output ""
$global:pcname = Read-Host "Please enter the desired PC Name"
Write-Output ""
$userinput = Read-Host "You've entered $pcname, is this correct? (Y/N)"
if($usrinput -like "*N*") {
GenName
#name of the parent function
}
Write-Output ""
The above code is part of a larger script that parses a computer name and assigns it to the correct OU in the end.
Our naming scheme works like this: BTS-ONE-LAP-000
So it is: Department - Location - Device Type - Device Count
The code will then take the first part "BTS-ONE" and parse it for the correct OU it should go to, and then assign it using the Add-Computer cmdlet. It will also rename the machine to whatever the user typed in ($pcname).
So, before it parses the name, I'd like it to search all current names in AD.
So, the user can type in: "BTS-ONE-LAP" and it will automatically find the next available Device Count, and add it to the name. So, it will automatically generate "BTS-ONE-LAP-041".
Added Note:
I've used Get-ADComputer -Filter 'Name -like "BTS-ONE-LAP-*"' | FT Name and the output is
Name
----
BTS-ONE-LAP-001
BTS-ONE-LAP-002
BTS-ONE-LAP-006
BTS-ONE-LAP-007
BTS-ONE-LAP-009
BTS-ONE-LAP-010
BTS-ONE-LAP-022
BTS-ONE-LAP-024
BTS-ONE-LAP-025
BTS-ONE-LAP-028
BTS-ONE-LAP-029
BTS-ONE-LAP-030
BTS-ONE-LAP-031
BTS-ONE-LAP-032
BTS-ONE-LAP-034
BTS-ONE-LAP-035
BTS-ONE-LAP-036
BTS-ONE-LAP-037
BTS-ONE-LAP-038
BTS-ONE-LAP-039
BTS-ONE-LAP-040
BTS-ONE-LAP-041
BTS-ONE-LAP-050
BTS-ONE-LAP-051
I don't know how to parse this so the code knows that BTS-ONE-LAP-003 is available (I'm terrible with arrays).
$list = (Get-ADComputer -Filter 'Name -like "BTS-ONE-LAP-*"' | Sort-Object Name[-1])
$i = 1
$found = $false
Foreach($Name in $list.Name)
{
while($i -eq [int]$Name.Split("-")[3].Split("-")[0])
{
$i++
}
}
$i
The above code will go through each name in the list, and will stop when it discovers say the 3rd computer in the set is NOT computer #3.
Example:
BTS-ONE-LAP-001 | $i = 1
BTS-ONE-LAP-002 | $i = 2
BTS-ONE-LAP-006 | $i = 3
It split BTS-ONE-LAP-006 to be 006, and convert it to an integer, making it 6.
Since 6 does not equal 3, we know that BTS-ONE-LAP-003 is available.
Another way could be to create a reusable function like below:
function Find-FirstAvailableNumber ([int[]]$Numbers, [int]$Start = 1) {
$Numbers | Sort-Object -Unique | ForEach-Object {
if ($Start -ne $_) { return $Start }
$Start++
}
# no gap found, return the next highest value
return $Start
}
# create an array of integer values taken from the computer names
# and use the helper function to find the first available number
$numbers = (Get-ADComputer -Filter 'Name -like "BTS-ONE-LAP-*"') |
ForEach-Object { [int](([regex]'(\d+$)').Match($_.Name).Groups[1].Value) }
# find the first available number or the next highest if there was no gap
$newNumber = Find-FirstAvailableNumber $numbers
# create the new computername using that number, formatted with leading zero's
$newComputerName = 'BTS-ONE-LAP-{0:000}' -f $newNumber
Using your example list, $newComputerName would become BTS-ONE-LAP-003
Note that not everything a user might type in with Read-Host is a valid computer name. You should add some checks to see if the proposed name is acceptable or skip the proposed name alltogehter, since all your machines are named 'BTS-ONE-LAP-XXX'.
See Naming conventions in Active Directory for computers, domains, sites, and OUs
There are two parts to this question.
I want to check if a local user exists or not before I go ahead and create it.
So far I've come up with a simple script to check if a local user exists or not. Here's the script to check if a user exists before I go ahead and create it.
$password = ConvertTo-SecureString -String "password" -AsPlainText -Force
$op = Get-LocalUser | Where-Object {$_.Name -eq "testuser1"}
if ( -not $op)
{
New-LocalUser testuser1 -Password $password | Out-Null
}
I tested this one out on my setup and it works fine for the most part without throwing any exception. Is there a better, quicker way to check if a user exists ? Also, is the script I'm using foolproof i.e. would it be better to handle it using ErrorAction or using try....catch ?
I'll be using this script for checking more than a couple of user accounts before I go ahead and create them.
Why is $op different in the following cases ?
CASE 1
CASE 2
I understand that Out-String is the reason behind this difference in output but I would've expected the output to have been more than just testuser1 in CASE 1.
I'm new to Powershell so can someone please help me understand why there's a difference in output ?
Use Try/Catch, most of the time it's faster to just ask and let Powershell handle the searching ;)
Especially with Long User lists, retrieving all the users and then iterating trough all of them will slow things down, just asking for a specific user is much faster but you need to handle the error if the user does not exist.
See example below:
Clear-Host
$ErrorActionPreference = 'Stop'
$VerbosePreference = 'Continue'
#User to search for
$USERNAME = "TestUser"
#Declare LocalUser Object
$ObjLocalUser = $null
try {
Write-Verbose "Searching for $($USERNAME) in LocalUser DataBase"
$ObjLocalUser = Get-LocalUser $USERNAME
Write-Verbose "User $($USERNAME) was found"
}
catch [Microsoft.PowerShell.Commands.UserNotFoundException] {
"User $($USERNAME) was not found" | Write-Warning
}
catch {
"An unspecifed error occured" | Write-Error
Exit # Stop Powershell!
}
#Create the user if it was not found (Example)
if (!$ObjLocalUser) {
Write-Verbose "Creating User $($USERNAME)" #(Example)
# ..... (Your Code Here)
}
About outputting certain data, I recommend that you explicitly define what you want to output, this way their will be no surprises and it makes thing clearer in your code.
See the example below, I explicitly defined the 3 properties I wanted and then forced it into a Table-View, to finish I converted it to a string, so no surprises for me any more ;)
Get-LocalUser | Select Name, Enabled, PasswordLastSet | Format-Table | Out-String
Example output
Name Enabled PasswordLastSet
---- ------- ---------------
Administrator False
DefaultAccount False
Gast False
Test-Gebruiker True 24-12-2017 01:58:12
You can use measure to check if user exist
$op = Get-LocalUser | where-Object Name -eq "yourusername" | Measure
if ($op.Count -eq 0) {
# DO SOMETHING
} else {
# REPORT SOMETHING
}
Write-Host calls ToString() method implementation of an object behind the scenes.
Write-Host $op
is equivalent to
Write-Host $op.ToString()
Get-LocalUser | Where-Object {$_.Name -eq "testuser1"} returns a LocalUser object. LocalUser.ToString() implementation returns Name property, that's why you see testuser1 as output.
Out-String on the other hand, converts the whole output of the command, which is a table representation of LocalUser object with 3 properties.
In Case 2, you can only use that output as a String object, whereas in Case 1, $op is a LocalUser object that you can manipulate or access properties. For example, you can print more properties:
Write-Host $op.Name, $op.Enabled, $op.Description
In order to see all the properties/methods available on an object, run Get-Member cmdlet:
$op | Get-Member
$UserID = Get-LocalUser -Name 'UserName' -ErrorAction SilentlyContinue
if($UserID){
Write-Host 'User Found'
}else{
Write-Host 'User Not Found'
} #end if user exists