Powershell notify when certificate almost expires - powershell

In Powershell I want to notify specific users when a certificate in a domain controller is gonna expire 24hour before hand. I already found a code then displays the start and expiry date and also the days remaining. But how can i get notified (through email) when the certificate expires.
Get-ChildItem Cert:\LocalMachine\My `
| Select #{N='StartDate';E={$_.NotBefore}},
#{N='EndDate';E={$_.NotAfter}},
#{N='DaysRemaining';E={($_.NotAfter - (Get-Date)).Days}}

I'd suggest adding some more properties to the objects you select for better recognition later and add a Where-Object{} clause to it, to filter for certificates that have 1 day or less left before expiring:
$certs = Get-ChildItem -Path 'Cert:\LocalMachine\My' |
Select-Object Thumbprint, Issuer,
#{N='StartDate';E={$_.NotBefore}},
#{N='EndDate';E={$_.NotAfter}},
#{N='DaysRemaining';E={($_.NotAfter - (Get-Date).Date).Days}} |
Where-Object { $_.DaysRemaining -le 1 }
if ($certs) {
# send an email to yourself
$mailSplat = #{
To = 'me#mycompany.com'
From = 'me#mycompany.com'
SmtpServer = 'MySmtpServer'
Subject = 'Expiring certificates'
Body = $certs | Format-List | Out-String
# other params can go here
}
# send the email
Send-MailMessage #mailSplat
}
(Get-Date).Date takes today's date as of midnight, not at the current time of day you run the script

Related

powershell to get exact date for passwordlastset

I'm working on scripts which send emails to users 14, 10, 7, 3 days before password expiring.
Password expires in 60 days.
If I set like below it works for accounts with 3 and less days to expiring. I don't want accounts with 2 days, 1 day, etc.
$ExpiringUsers = $AdUsers | where PasswordLastSet -lt (get-date).AddDays(-57)
If I set like below it doesn't work at all
$ExpiringUsers = $AdUsers | where PasswordLastSet -eq (get-date).AddDays(-57)
How to set equal 3 days not more not less.
Thanks!
You need to define and filter by a time range, for example, set equal 3 days not more not less would be like this:
$start = [datetime].AddDays(-3) # => April 2 at 12:00 AM
$finish = [datetime].AddDays(-2) # => April 3 at 12:00 AM
# Note `-lt $finish` instead of `-le $finish`
$AdUsers | Where-Object { $_.PasswordLastSet -ge $start -and $_.PasswordLastSet -lt $finish }
A better approach is to use msDS-UserPasswordExpiryTimeComputed for checking how long until the account credential expires:
$serviceAccounts =
'acct1#domain.tld',
'acct2#domain.tld
$daysLeft = 20
$expiringAccounts = ( $serviceAccounts | Foreach-Object {
Get-AdUser -Filter "(UserPrincipalName -eq '$_' -Or sAMAccountName -eq '$_' ) -And ( PasswordNeverExpires -eq 'false' )" -Properties msDS-UserPasswordExpiryTimeComputed |
Where-Object { ( New-TimeSpan -Start $currentDate -End ( [DateTime]::FromFileTime( $_.'msDS-UserPasswordExpiryTimeComputed' ) ) ).Days -le $daysLeft }
} )
As written, this code will gather the accounts expiring within 20 days. Adjust $daysLeft to control the remaining threshold until expiry.
Note that [DateTime]::FromFileTime is required to transpose the value ofmsDS-UserPasswordExpiryTimeComputed from the file-time format it is stored in AD as to a workable DateTime object.
You can define the account as a sAMAccountName or in Universal Principal Name (UPN) format.
This also exemplifies using AD filters in order to have AD return only the objects you need for this query, minimizing the local processing performed with Where-Object.
The notable exception is msDS-UserPasswordExpiryTimeComputed; because it is a Constructed Attribute within Active Directory, neither AD filters or LDAP filters can evaluate it and so this must be done via additional local processing.
Here is an answer I have also written which goes into deeper detail on using the AD filter syntax.
What is wrong with PasswordLastSet for checking credential expiration if you know the account's lifecycle?
The problem with checking PasswordLastSet is that, while it works, if the threshold until expiry changes on the Active Directory side, your script will need to be updated or it will incorrectly identify accounts whose credential is set to expire soon. It also becomes more difficult to track which accounts are beholden to different lifecycles in environments where not all accounts are beholden to the same security policies.

Get the Last Logon Time for Each User Profile

I need to get the last logon time for each user profile on a remote computer (not the local accounts). I've tried something similar to the following using Win32_UserProfile & LastLogonTime, however this is not giving accurate results. For example, one this computer, only 1 account has been used in the past year, however LastUpdateTime is showing very recent dates. Some accounts have not even been logged into and should say "N/A", but it doesn't.
$RemoteSB_UserADID = Get-WmiObject win32_userprofile -Property * | Where-Object {$_.LocalPath -like "*users*"} | Sort-Object $_.LastUseTime | ForEach-Object{
$Parts = $_.LocalPath.Split("\")
$ADID = $Parts[$Parts.Length - 1]
if ($ADID -ne "SPECIALPURPOSEACCOUNT1" -and $ADID -ne "SPECIALPURPOSEACCOUNT2"){
$Time = $null
try{
$Time = $_.ConvertToDateTime($_.LastUseTime)
}catch{
$Time = "N/A"
}
"[$ADID | $Time]"
}
}
Example Output
[Acct1 | 03/13/2022 07:18:19]
[Acct2 | 03/15/202214:59:16]
[Acct3 | 03/13/2022 07:18:19]
[Acct4 | 03/16/2022 11:53:17] <--- only "active" account
How can I go about retrieving accurate (or decently accurate) login times for each user profile? Thanks!
It would help to know for what reason you need that, so that I know how to find a (better) solution for you.
If you need to cleanup your profiles not used for a long time at the target system, then take the last changed date of "ntuser.dat". That is the last logon if you define logon like logging on to a new session. If the user was logged on and simply locked the computer or used standby and then relogs then this date won't change.
Use this to get this date from all users you have access to but possibly not getting real user names
Get-ChildItem \\REMOTECOMPUTERNAMEHERE\Users\*\ntuser.dat -Attributes Hidden,Archive | Select #{Name="NameByFolder";Expression={($_.DirectoryName -split "\\")[-1]}},LastWriteTime
Or this a bit more complex version
Invoke-Command -ComputerName REMOTECOMPUTERNAMEHERE -ScriptBlock {$UsersWithProfilePath = #{}
dir "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList" |
where {$_.name -like "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList\S-1-5-21-*"} |
foreach {$UsersWithProfilePath[([System.Security.Principal.SecurityIdentifier]$_.name.split("\")[-1]).Translate( [System.Security.Principal.NTAccount]).Value] = $_.GetValue("ProfileImagePath")}
foreach ($Name in $UsersWithProfilePath.Keys) {#{$Name =(dir (join-path $UsersWithProfilePath.$Name ntuser.dat) -Attributes Hidden,Archive,System).LastWriteTime}}}
Depending on what you need you need to change it a bit.
Sorry for the long codelines... it is late here.

Create a PowerShell script that would get the last 30 days history logon of Domain Admin member

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

Select-String -Context and find email address in data

I have a text file where variations of this data (the number after 'SVC' and the date before, along with the text body) will appear multiple times. I can capture the string of data, but once I do, I need to locate an email address inside that data. The email may appear in the context at any line 4 through 9. I can't seem to figure out how to isolate the data and set it as a variable so it can be captured.
Select-String $WLDir -pattern '(\d{2}:\d{2}) - (\d{2}:\d{2})(PMT[S|T]\d{8})' -Context 0,9 | ForEach-Object {
$StartTime=[datetime]::ParseExact($_.Matches.Groups[1].Value,"HH:mm",$null)
$EndTime=[datetime]::ParseExact($_.Matches.Groups[2].Value,"HH:mm",$null)
$ElapsedTime = (NEW-TIMESPAN –Start $StartTime –End $EndTime).TotalHours
$Email = Select-String $_. -pattern '(\b[A-Z0-9._%+-]+#[A-Z0-9.-]+\.[A-Z]{2,4}\b)'
[PSCustomObject]#{
SO = $_.Matches.Groups[3].Value
Topic = $_.Context.PostContext[0]
Status = $_.Context.PostContext[1]
ElapsedHrs = $ElapsedTime
Email = $Email
}
} | Export-Csv $ExportCsv -NoTypeInformation
My example file is like this:
09:45 - 10:15SVC1234567 | Sev8 |437257 | COMPANY | Due: 12/28/2016
WORK TITLE
- - Preferred Customer (Y/N): Y Phone: 000-000-0000 ANY Hardware (Y/N): N
DATA on file (Y/N/NA): Y Contact: Person Name Full Address: 1234 PANTS
XING, RM/STE 100,NEWARK, NJ, 00000 - Hours: 8-5 Issue: Install admin
and others Fax Number: NA (required for all cases sent to LOCATION or
LOCATION_EXCPT Provider Groups) E-Mail address: email#location.com the
customer speak English? yes Escalation Approved By (Name/ID): Guy
aljdfs ITEM Product: PRODUCTNAME Group:THIS ONE Include
detailed notes below, including reason for severity: SCHEDULED WORK
------------------------------ NOTES: -Cx requesting a tech on site -Cx
wants to install WS and wants to be assisted in other concerns
I've tried capturing the email in the context with $Email = Select-String $_. -pattern '(\b[A-Z0-9._%+-]+#[A-Z0-9.-]+\.[A-Z]{2,4}\b)', $Email = Select-String $_.WLDir -pattern '(\b[A-Z0-9._%+-]+#[A-Z0-9.-]+\.[A-Z]{2,4}\b)' and $Email = Select-String $_.Context -pattern '(\b[A-Z0-9._%+-]+#[A-Z0-9.-]+\.[A-Z]{2,4}\b)' but can't figure out how to call back the context to search it for the email address. There's also a good chance I'm doing this all wrong. Does anyone know how I can capture this and set it as a variable?
Try this:
$content = gc -path $path -Raw | Out-String
$regex1 = [regex]"\w+#\w+.\w+"
$regex2=[regex]"(?ms)(\d{2}:\d{2}) - (\d{2}:\d{2})(\D+)(\d+)(.*)"
$content | Select-String -pattern $regex2 | %{
$startTime = [datetime]::ParseExact(($regex2.Matches($content) | %{$_.Groups[1].Value}),"HH:mm",$null)
$endTime = [datetime]::ParseExact(($regex2.Matches($content) | %{$_.Groups[2].Value}),"HH:mm",$null)
$elapsedTime = (NEW-TIMESPAN –Start $startTime –End $endTime).TotalHours
$code = "PMT" + ($_.Matches.Groups[4].value)
$remainingString = $_.Matches.Groups[5].Value
$topic = $remainingString.split("`n")[1]
$status = $remainingString.split("`n")[2]
$email = $regex1.Matches($remainingString).Value
[PSCustomObject]#{
SO = $code
Topic = $topic
Status = $status
ElapsedHrs = $elapsedTime
Email = $email
}
} | Export-Csv "res.csv" -NoTypeInformation
Because I never found an accurate way to capture this information, I decided to capture all lines 0-9 in post context into Status. On the Excel sheet, I'm using the calculation from this page of =IF(O6="","",TRIM(RIGHT(SUBSTITUTE(LEFT(O6,FIND(" ",O6&" ",FIND("#",O6))-1)," ",REPT(" ",LEN(O6))),LEN(O6)))) to pull the data from column "O" to column "Q" where the email belongs. I appreciate everyone's assistance.

Script to count number of emails for multiple users

I am needing a little help with my script. I have this working if I use just a single email address. I need to add a list of 8 emails addresses for this to scan. How would I modify this to send 1 email for all 8 users?
I have seen scripts that make a html file that displays everything in a nice table but those are ran against all users in exchange and I only needs this for a group of 8 users.
Add-PSSnapin Microsoft.Exchange.Management.PowerShell.E2010
#Powershell blah blah blah
$nl = [Environment]::NewLine
#Mailbox to gather stats on
$mailboxs=$mailbox= 'user1#domain.com','user2#domain.com'
#Get todays
$startDate=Get-Date
$endDate=Get-Date
#Subtract 1 day from todays date (report ending day) and 1 day from todays date (report starting day)
$startDateFormatted=$startDate.AddDays(-1).ToShortDateString()
$endDateFormatted=$endDate.AddDays(-1).ToShortDateString()
foreach ($mailbox in $mailboxs)
{
# Sent e-mails
$sendCount = Get-TransportService | Get-MessageTrackingLog -Start "$startDateFormatted 00:00:00" -End "$endDateFormatted 23:59:59" -Sender $mailbox -resultsize unlimited | select-object -unique MessageId
# Received e-mails - This works but not on generic accounts
$receiveCount = Get-TransportService | Get-MessageTrackingLog -Start "$startDateFormatted 00:00:00" -End "$endDateFormatted 23:59:59" -Recipients $mailbox -resultsize unlimited | select-object -unique MessageId
$sendCountString = $sendCount.count
$receiveCountString = $receiveCount.count
}
$Output =
$Mailbox |
foreach {
$ResultHash =
#{
Address = $_
Sent = $Sendcountstring
Received = $Receivecountstring
}
New-Object -TypeName PSObject -Property $ResultHash |
Select Address,Sent,Received
}
#Who to send the e-mail report to.
#Multiple e-mail addresses should be in this format "<email1#domain.com>, <email2#domain.com>"
$MailParams = #{
From = "ISSReports#domain.com"
To = "user3#domain.com"
subject = "Daily e-mail report for ISS for $startDateFormatted"
BodyAsHTML = $true
smtpServer = "mail.domain.com"
}
$header =
#"
"Mailbox Stats
Report date range: $startDateFormatted 00:00:00 - $endDateFormatted 23:59:59
"#
$body = $Output | ConvertTo-Html -As Table -Head $header | out-string
Send-MailMessage #MailParams -Body $body
I put this together from your script and some pieces borrowed from a couple of my own. For this kind of reporting, you only need to read through the logs once, and you can eliminate the need to de-dupe by messageid by only returning the Deliver events. There will be one deliver event per email sent, containing both the sender and recipients.
#Mailboxs to gather stats on
$mailbox= 'user1#domain.com','user2#domain.com'
#Get todays date twice
$startDate=Get-Date
$endDate=Get-Date
#Hash tables for send and receive counts
$Sent = #{}
$Received = #{}
#Subtract 1 day from todays date (report ending day) and 1 days from todays date (report starting day)
$startDateFormatted=$startDate.AddDays(-1).ToShortDateString()
$endDateFormatted=$endDate.AddDays(-1).ToShortDateString()
$TransportServers = Get-ExchangeServer |
Where {$_.serverrole -match "hubtransport"} |
Select -ExpandProperty Name
foreach ($TransportServer in $TransportServers)
{
Get-MessageTrackingLog -Start "$startDateFormatted 00:00:00" -End "$endDateFormatted 23:59:59" -EventID Deliver -Server $TransportServer -ResultSize Unlimited |
foreach {
if ($mailbox -contains $_.sender )
{ $Sent[$_.Sender]++ }
foreach ($Recipient in $_.Recipients)
{
if ($mailbox -contains $Recipient )
{ $Received[$Recipient]++ }
}
}
}
$Output =
$Mailbox |
foreach {
$ResultHash =
#{
Address = $_
Sent = $Sent[$_]
Received = $Received[$_]
}
New-Object -TypeName PSObject -Property $ResultHash |
Select Address,Sent,Received
}
#Who to send the e-mail report to.
#Multiple e-mail addresses should be in this format "<email1#domain.com>, <email2#domain.com>"
$MailParams = #{
From = "ISSReports#domain.com"
To = "user3#domain.com"
subject = "Weekly e-mail report for $mailbox for $startDateFormatted - $endDateFormatted"
BodyAsHTML = $true
smtpServer = "mail.domain.com"
}
$header =
#"
"Mailbox Stats
Report date range: $startDateFormatted 00:00:00 - $endDateFormatted 23:59:59
"#
$body = $Output | ConvertTo-Html -As Table -Head $header | out-string
Send-MailMessage #MailParams -Body $body
For these 8 mailboxes you need a count of messages sent by that mailbox? This calls for 3 advanced functions:
count the mail
create a report
send the report
So, I see it like this (mostly pseudo-code):
Function Count-SentFromMbx {
#paramter $mailbox
#parameter $startdate
#parameter $enddate
# function counts sent mail per your working single user script
# or better yet, per mjolinor's script leveraging `-contains`
# build custom object with mailbox and count as properties
# or better yet, enough information to build a digest.
# return object/collection
}
# Create a function to format the results email
# Create a function to send the results email
# Define collection of mailboxes
$mailboxes = "blah#blah.com","blah2#blah.com",etc.
# define or get the dates
$startdate = whatever
$enddate = whatever
# initialize array for results
$results = #{}
# set other variables for storage of the report
# and for recipient list
# and anything else needed 'globally'
# Pull data from the logs:
$results += Count-SentFromMbx -mailbox $mailbox -startdate $startdate -enddate $enddate
# if you end up with a collection of collections, you may need to change to
# $results = ... and add a line like `$results | $reportData += $_`
# build report using report function
# send report using send function
The main idea I am suggesting is to use advanced functions (Get-Help about_advanced_functions) for each modular task involved in the process. Then you can leverage the power of objects to make gathering the data easy. You can tailor your reports for your audience by tweaking the reports function.
Having your function return an object, try $results | ConvertTo-Html. Bare minimum, if you have to build your own report, the collection of objects, $results, gives you an organized data source, easily passed through a foreach loop. Using objects you could even expand the collection to return message digests instead of just the number of messages sent. Instead of counting messages, create a custom object with subject, recipient and the sender. (Easy way to create object is to pipe objects to select with a customized property list). Return that collection and add it to the results collection. Then reporting could include message digests AND totals. The ability of objects to carry information gives you great flexibility.
One of the key things that mjolinor did was to put your 8 addresses in a collection. When looping through, you can compare if ($collection -contains $suspect) { do stuff}. This executes the block if the $suspect is in the $collection. This allows you to loop through the logs once.