I am trying to write a script to search through AD and look for any stale computers and then sends an email to me. However, there are 3 requirements:
get the lastlogondate for any pc that is older than 90 days
once I get that list, I do a ping test
from the ping test, I take that and get the last time the password was set
Import-Module activedirectory
#Date = today's date -90 days
$date=[datetime]::Today.adddays(-90)
$pcArray = #()
$bodyArray=#()
$passArray=#()
$oldpc = Get-ADComputer -properties * -Filter 'lastlogondate -le $date' |
where {$_.name -like '*WD*'} |select name, lastlogondate
foreach ($pc in $oldpc)
{
if (!(Test-Connection $pc.name -quiet))
{
$script:pcArray += $pc.name
}
}
foreach ($failping in $pcArray)
{
$lastpass = Get-ADComputer $failping -properties * |
select name, passwordlastset
$script:passArray += $lastpass
}
#Send email
foreach ($badpc in $oldpc)
{
$script:bodyArray += $badpc
}
if($bodyArray)
{
Send-MailMessage -to "toEmailaddress "-Subject "Stale Computers Check" -From "fromEmailaddress "-SmtpServer "smtpserver" -BodyAsHtml "The following computers have not been logged into for over 90 days </br>$bodyArray </br></br>
The following computers are not pingable</br>$pcArray </br></br>The last password changed on those pcs are </br>$lastpass"
}
The email I get looks like this:
The following computers have not been logged into for over 90 days
The following computers are not pingable
pcname1 pcname2
The last password changed on those pcs are
#{name=pcname1; passwordlastset=12/08/2014 14:59:38}
As you can see, the $bodyArray variable isn't being sent yet after I run the script, in PowerShell, I call $bodyArray, and I get the following output:
name lastlogondate
---- -------------
pcname1 9/13/2014 8:06:21 PM
pcname2 9/17/2014 5:25:25 PM
pcname3 12/5/2014 11:16:16 AM
pcname4 12/8/2014 3:00:01 PM
I tried ToString() already and iterating as you can see above.
As for number 2, I would like to get the PC names each on a newline. I tried join + `n, but that didn't work.
And for number 3, I can only get 1 PC to output and not the entire array. Running via pscmd I also get 1 PC, but there should be 2 since I am looping through 2 PCs in $pcArray.
Also if you want it to look more nice and readable you can do something like this that will spit it out in a table:
$body += "<body><table width=""560"" border=""1""><tr>"
$bodyArray[0] | ForEach-Object {
foreach ($property in $_.PSObject.Properties){$body += "<td>$($property.name)</td>"}
}
$body += "</tr><tr>"
$bodyArray | ForEach-Object {
foreach ($property in $_.PSObject.Properties){$body += "<td>$($property.value)</td>"}
$body += "</tr><tr>"
}
$body += "</tr></table></body>"
The $bodyArray[0] | ForEach-Object prints out the column names as a table header. and the $bodyArray | ForEach-Object prints out all the values to the table. And if you want to get fancy you can even add some CSS to make the tables look better
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 would like to write a Power Shell script that would do the following:
- If the user is member of (Domain admins) get me the last 30 days history logon of this user in any Domain joined computer.
I created something now but it still lacks a lot as it reads the security events on the Domain controller and brings the users,time and matches them with the Domain admin group as in the attached screenshot
I would appreciate if someone can help me evolve this script into something useful
$Rusers = Get-WinEvent -Computer dc02 -FilterHashtable #{Logname='Security';ID=4672} -MaxEvents 50 |
` select #{N='User';E={$_.Properties[1].Value}},TimeCreated
$DAUsers = Get-ADGroupMember -Identity "Domain Admins"
Foreach ($DAUser in $DAUsers){
$DomainUser = $DAUser.SamAccountName
foreach ($Ruser in $Rusers){
$RAUser = $Ruser.User
If ($RAUser -match $DomainUser){
Write-Host $Ruser is domain admin }
}[![enter image description here][1]][1]
}
# Get domain admin user list
$DomainAdminList = Get-ADGroupMember -Identity 'Domain Admins'
# Get all Domain Controller names
$DomainControllers = Get-ADDomainController -Filter * | Sort-Object HostName
# EventID
$EventID = '4672'
#
# Get only last 24hrs
$Date = (Get-Date).AddDays(-1)
# Limit log event search for testing as this will take a LONG time on most domains
# For normal running, this will have to be set to zero
$MaxEvent = 50
# Loop through Dcs
$DALogEvents = $DomainControllers | ForEach-Object {
$CurDC = $_.HostName
Write-Host "`nSearching $CurDC logs..."
Get-WinEvent -Computer $CurDC -FilterHashtable #{Logname='Security';ID=$EventID;StartTime = $Date} -MaxEvents $MaxEvent |`
Where-Object { $_.Properties[1].Value -in $DomainAdminList.SamAccountName } |`
ForEach-Object {
[pscustomobject]#{SamAccountName = $_.Properties[1].Value;Time = $_.TimeCreated;LogonEventLocation = $CurDC}
}
}
All the Domain Admin logon events should now be in $DALogEvents
You'll need to group results by name, then export to a file
Thanks a lot for your help, I apologize I was not clear enough. The kind of information I am looking for is pertaining to users who have been utilized for services e.g. (SQL reporting Services, Or Sccm Service ..etc )
This script does what I want but it doesn't run only for domain admin users, it runs for everyone basically and not sure if there's a limit to the time/date.
Is it possible to adjust it to let it run against Domain Admin users for 30 days and print information like. Source IP, User, Target Dc, Date?
Get-EventLog -LogName Security -InstanceId 4624 |
ForEach-Object {
# translate the raw data into a new object
[PSCustomObject]#{
Time = $_.TimeGenerated
User = "{0}\{1}" -f $_.ReplacementStrings[5], $_.ReplacementStrings[6]
Type = $_.ReplacementStrings[10]
"Source Network Address" = $_.ReplacementStrings[18]
Target = $_.ReplacementStrings[19]
}
}
I've added couple more of custom objects to get the result that I needed. I think turning this into a function would be great tool to use for auditing.
Thanks a lot to you #Specialist
# Get domain admin user list
$DomainAdminList = Get-ADGroupMember -Identity 'Domain Admins'
# Get all Domain Controller names
$DomainControllers = Get-ADDomainController -Filter * | Sort-Object HostName
# EventID
$EventID = '4624'
#
# Get only last 24hrs
$Date = (Get-Date).AddDays(-3)
# Limit log event search for testing as this will take a LONG time on most domains
# For normal running, this will have to be set to zero
$MaxEvent = 100
# Loop through Dcs
$DALogEvents = $DomainControllers | ForEach-Object {
$CurDC = $_.HostName
Write-Host "`nSearching $CurDC logs..."
Get-WinEvent -ComputerName $CurDC -FilterHashtable #{Logname='Security';ID=$EventID;StartTime = $Date} -MaxEvents $MaxEvent |`
Where-Object { $_.Properties[5].Value -in $DomainAdminList.SamAccountName } |`
ForEach-Object {
[pscustomobject]#{SourceIP = $_.Properties[18].Value; SamAccountName = $_.Properties[5].Value;Time = $_.TimeCreated;LogonEventLocation = $CurDC}
}
}
$DALogEvents
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
So I read stackoverflow a lot and it always helps me with my understanding, at the moment, I'm a bit stumped on the matching any of multiple variables and as such, thought I'd ask the exports.
A bit of background on my task.
I am a systems admin for an AD forest with multiple child domains (+4), we are going through a process to consolidate these domains into one - however, unfortunately, as each was managed independently we have comes across the age old 'duplicate SAM' issue (and will for sure come across dupe UPNs too once we start digging).
As such - I am trying to build a script to find these duplicates so I can address them with the teams who support the users in these domains.
I've got a fair way through this, I've exported a list of the SAMs for one migrating domain, and I can parse this into PS and have it look up each SAM against all of my domains and either give me a variable with the username OR $null.
So, I now just want to understand what the 'IF' command is for matching more than one variable and showing which ones match.
So, for example, from these variables...
$one="MSmith01"
$oneNT="DOMAIN1\MSmith01"
$two="MSmith01"
$twoNT="DOMAIN2\MSmith01"
$three=$null
$threeNT=$null
$four=$null
$fourNT=$null
$five="MSmith01"
$fiveNT="DOMAIN5\MSmith01"
$six=$null
$sixNT=$null
How do I write the IF command to show me if more than one variable matches??
If I write this:
if ($one -match $two -match $three -match $four -match $five -match $six) {write-host "Duplicate Found - $oneNT, $twoNT, $threeNT, $fourNT, $fiveNT, $sixNT." -foregroundcolor "yellow"}
It will show me :
Duplicate Found - DOMAIN1\MSmith01, DOMAIN2\MSmith01, , , DOMAIN5\MSmith01,.
But, if the variables are:
$one=$null
$oneNT=$null
$two="MSmith01"
$twoNT="DOMAIN2\MSmith01"
$three="MSmith01"
$threeNT="DOMAIN3\MSmith01"
$four=$null
$fourNT=$null
$five=$null
$fiveNT=$null
$six=$null
$sixNT=$null
Then the result seems to ignore the fact that DOMAIN 2 and DOMAIN3 have matching SAMs and it appears that "x -match y -match z" is really actually only comparing x and y, so I get nothing.
Which is obviously incorrect.
Can anyone explain how I compare and look for ANY duplicate amongst several variables?
Thanks
To further elaborate:
To get my variables I'm doing this:
$SAMAccountList=gc "C:\My Documents\Domain2SAMList.txt"
$SamAccountList | % {
$one=Get-QADUser -SamAccountName $_ -IncludedProperties SamAccountName -Server dc.domain1.local | foreach { $_.SAMAccountName }
$oneNT=Get-QADUser -SamAccountName $_ -IncludedProperties NTAccountName -Service dc.domain1.local | foreach { $_.NTAccountName }
$twoSAM=Get-QADUser -SamAccountName $_ -IncludedProperties SamAccountName -Service dc.domain2.local | foreach { $_.SAMAccountName }
$twoNT=Get-QADUser -SamAccountName $_ -IncludedProperties NTAccountName -Service dc.domain2.local | foreach { $_.NTAccountName }
#...
#...
#...etc for each domain
}
This then gives me a set of variables as I mentioned above
I would put them in a customobject and search with the unique names.
$NAMES = #()
$NAMES += [pscustomobject] #{name=$one;nameNT=$oneNT}
$NAMES += [pscustomobject] #{name=$two;nameNT=$twoNT}
$NAMES += [pscustomobject] #{name=$three;nameNT=$threeNT}
$NAMES += [pscustomobject] #{name=$four;nameNT=$fourNT}
$NAMES += [pscustomobject] #{name=$five;nameNT=$fiveNT}
$NAMES += [pscustomobject] #{name=$six;nameNT=$sixNT}
$UniqueNames=($NAMES | Sort-Object -Property name -Unique).Name
ForEach($Uniquename in $UniqueNames){
if($Uniquename -ne $NULL){
$SEARCH_RESULT=$NAMES|?{$_.Name -eq $Uniquename}
if ( $SEARCH_RESULT.Count -gt 1)
{
$SEARCH_RESULT
}
}
}
I get the following results
First data set
name nameNT
---- ------
MSmith01 DOMAIN1\MSmith01
MSmith01 DOMAIN2\MSmith01
MSmith01 DOMAIN5\MSmith01
Second data set
name nameNT
---- ------
MSmith01 DOMAIN2\MSmith01
MSmith01 DOMAIN3\MSmith01
I'm creating a script that tells me the creation / modification date and other pieces of info of AD objects to determine upgrade status of the machines in large domains. I have no problem accomplishing this in a well formatted and easy to read manner in Server 2008 because it has Active Directory modules, but this isn't the case with Server 2003.
With server 2003 I had to use a different approach to the script to gather the information I want, but I am unsure how to format this.
This is my current script:
$filePath = “$ENV:UserProfile\Desktop\output.txt”
## Enter the name of the Domain controller below
$DCName = “Exchange”
$computers = dsquery computer domainroot -name * -limit 0
Foreach ($computer in $computers) {
repadmin /showattr $DCName $computer /atts:"WhenChanged,WhenCreated,OperatingSystem" /long | out-file –append $filepath
}
This is the sample output:
DN: CN=Sample-Object,CN=Computers,DC=Contoso,DC=com
1> whenCreated: 07/04/2011 14:00:02 Pacific Standard Time Pacific Daylight Time
1> whenChanged: 08/09/2012 11:24:22 Pacific Standard Time Pacific Daylight Time
1> operatingSystem: Windows 7 Professional
In server 2008 I'm able to use string formatting ('"{0}","{1}"' -F $computer.name, $computer.whatever) amd output it to a .csv to make it presentable but I don't think the same methods will apply to the results of repadmin.
My end goal would to simply have a CSV with Computer Name, along with the three or however many attributes I have extracted from repadmin.
Any help appreciated, thank you.
Give this a try, you can export it to CSV and import it back as objects:
$result = dsquery computer domainroot -name * -limit 0 | foreach {
repadmin /showattr $DCName $_ /atts:"WhenChanged,WhenCreated,OperatingSystem" /long
} | Out-String
$result.split([string[]]'DN:',[StringSplitOptions]::RemoveEmptyEntries) | Foreach-Object{
$attr = $_.Split("`r`n",[StringSplitOptions]::RemoveEmptyEntries)
New-Object -TypeName PSObject -Property #{
DN = $attr[0].Trim()
whenCreated = $attr[1].Trim() -replace '^1> whenCreated: '
whenChanged = $attr[2].Trim() -replace '^1> whenChanged: '
operatingSystem = $attr[3].Trim() -replace '^1> operatingSystem: '
}
}