Powershell script to find currently bound expiring certificates in IIS remotely - powershell

I'm currently working on a script that will send an email once the certificates binded in my web servers' IIS are nearing there expiration date. I do have the script to send it in email. All I need to know is how to compare the certificates available in the store query versus the certificates currently in use. For now, here's what I have:
$Date= (Get-Date)
$SMTPServer = "smtp.test.com"
$From = "testmail#noreply.com"
Import-Module WebAdministration
$Servers = #("WEBSERVER1", "WEBSERVER2")
$certificates = foreach($server in $Servers){
Invoke-Command -ComputerName $server -ScriptBlock { $CertAll = Get-ChildItem -Path Cert:\LocalMachine\My }
Invoke-Command -ComputerName $server -ScriptBlock { $CertInUse = Get-ChildItem -Path IIS:\SslBindings }
Invoke-Command -ComputerName $server -ScriptBlock { $CertSame = Compare-Object -ReferenceObject $CertAll -DifferenceObject $CertInUse -Property Thumbprint -IncludeEqual -ExcludeDifferent }
Invoke-Command -ComputerName $server -ScriptBlock { $cert = $CertSame | ForEach {Get-ChildItem -Path Cert:\LocalMachine\My\$($_.thumbprint)} |
Select-Object Subject, DaysUntilExpired, NotAfter, #{n='ExpireInDays';e={($_.notafter - ($Date)).Days}}}
}
$certificates | Sort DisplayName
Any help and suggestion would be appreciated. Thanks!

The script above never works as you are creating variables in separate sessions to the same computer.
You can do it in two ways.
Create a session object targeting the destination server once and reuse it. So that you will be able to get the variables defined in the session in subsequent Invoke-command executions.
Without creating a session object, but by executing everything on the remote server in a single Invoke-Command .
example:-
Invoke-command -computerName $Server {
$CertAll = ....
$CertInUse = ....
$CertSame = ....
$cert = $CertSame | ForEach ..... |
Select-Object Subject, DaysUntilExpired .....
}
if you don't have any further actions on the remote server after identifying the certiricates expire date, I would suggest to use the second option.

#PRASOON I already managed to check my certificates remotely. I try to work with different references I found in google. Anyhow, here's the script.
$Date = Get-Date
$servers = Get-Content C:\servers.txt
$cert = Foreach ($server in $servers) {
Invoke-Command -ComputerName $server -ScriptBlock{
Import-Module WebAdministration; Get-ChildItem -Path IIS:SslBindings | ForEach-Object -Process{
if ($_.Sites)
{
$certificate = Get-ChildItem -Path CERT:LocalMachine\My |
Where-Object -Property Thumbprint -EQ -Value $_.Thumbprint
[PSCustomObject]#{
Sites = $_.Sites.Value
DnsNameList = $certificate.DnsNameList
NotAfter = $certificate.NotAfter
ExpireInDays = ($certificate.NotAfter - (Get-Date)).Days}
}
}
}
}
$cert | Select PSComputerName, DnsNameList, NotAfter, ExpireInDays | Where-Object {$_.ExpireInDays -lt 30} | Out-File C:\results.txt
So basically, this will display the certficates which will expire exactly or within 30 days from now. I am still working on this because what I'm trying to do is to send an email when the script detects a certificate that will expire 30 days from the current date and send an email notification. I will ask my concern about that in another post.

Related

How can I use Powershell to find when an SSL certificate expires for ONLY IIS for a list of servers from OU?

I have this section of code that if I can merely get the script to ONLY reply with Subject that exists (which indicates the IIS cert), then I can be done... (I have the OU enumeration, and the Invoke section down, and the email of the file for scheduling in a task):
[NOTE: I have the expiration set to 500 days so I can then use the script later to merely find specific expiration times]
[NOTE2: $day is set in my $profile to '$day = Get-Date -Format yyyyMMdd']
$serverlist = $serverListpath.Name
foreach($server in $serverlist){
if($server -like '#*')
{
continue
}
$threshold = 500 #Number of days to look for expiring certificates
$deadline = (Get-Date).AddDays($threshold) #Set deadline date
$p = ($c++/$server.count) * 100
Write-Progress -Activity "Checking $._" -Status "$p % completed" -PercentComplete $p;
if(Test-Connection -ComputerName $server -Count 2 -Quiet){
#$server = "KnownIISServerHostname" #<-- to test with a hostname
Invoke-Command -Verbose -ComputerName $server { Dir Cert:\LocalMachine\My } |`
foreach {
If ($_.NotAfter -le $deadline) {
$_ | Select *| select PSComputerName, Subject, NotAfter, #{Label="Expires In (Days)";Expression={($_.NotAfter - (Get-Date)).Days}} }
}|`
select PSComputerName,Subject, NotAfter, #{Label="Expires In (Days)";Expression={($_.NotAfter - (Get-Date)).Days}} |`
export-csv -Force -Append -Encoding ASCII -NoTypeInformation .\output\$day-ExpiringIISSSLCerts.csv
}
}
So where do I tweak this to get the reply to ONLY have existing "Subject" fields; Not to get the null subject field replies (which are RDP certificates)
Try to use this:
Import-Module WebAdministration
$CertAll=Get-ChildItem -Path Cert:\LocalMachine\My
$CertInUse=Get-Childitem -Path IIS:\SslBindings
$CertSame=Compare-Object -ReferenceObject $CertAll -DifferenceObject $CertInUse -Property ThumbPrint -IncludeEqual -ExcludeDifferent
$CertSame | foreach{Get-Childitem –path Cert:\LocalMachine\My\$($_.thumbprint)} | Select-Object -Property Subject, #{n=’ExpireInDays’;e={($_.notafter – (Get-Date)).Days}}
Since IIS certificates are your scope of concern here, I would suggest using the IIS PowerShell module to make sure you're selecting only certificates that are actually in use by IIS.
The following should pull certificates attached to sites with HTTPS(SSL). I don't currently have multiple sites on a single IIS server for testing, but theoretically this should find all of them, not just the "Default Web Site."
$serverlist = $serverListpath.Name
foreach($server in $serverlist){
if($server -like '#*')
{
continue
}
$threshold = 500 #Number of days to look for expiring certificates
$deadline = (Get-Date).AddDays($threshold) #Set deadline date
$p = ($c++/$server.count) * 100
Write-Progress -Activity "Checking $._" -Status "$p % completed" -PercentComplete $p;
if(Test-Connection -ComputerName $server -Count 2 -Quiet){
#$server = "KnownIISServerHostname" #<-- to test with a hostname
#Pull certificates from existing IIS bindings
$certificates = Invoke-Command -Verbose -ComputerName $server {
Import-Module IISAdministration
$sitebindings = Get-IISSite | foreach { Get-IISSiteBinding -Protocol HTTPS -Name $_ }
$thumbprints = $sitebindings.Attributes | where {$_.Name -match "certificateHash"} | Select-Object -ExpandProperty Value
$thumbprints | foreach {dir Cert:\LocalMachine\My\$_}
}
$certificates |`
foreach {
If ($_.NotAfter -le $deadline) {
$_ | Select *| select PSComputerName, Subject, NotAfter, #{Label="Expires In (Days)";Expression={($_.NotAfter - (Get-Date)).Days}} }
}|`
select PSComputerName,Subject, NotAfter, #{Label="Expires In (Days)";Expression={($_.NotAfter - (Get-Date)).Days}} |`
export-csv -Force -Append -Encoding ASCII -NoTypeInformation .\output\$day-ExpiringIISSSLCerts.csv
}
}
#Complete LOCAL run script. Call this in a Foreach Invoke-command.
$CertAll=GCI -Path Cert:\LocalMachine\My
$CertInUse= (GCI IIS:SSLBindings)
$CertSame=Compare-Object -ReferenceObject $CertAll -DifferenceObject $CertInUse -Property ThumbPrint -IncludeEqual -ExcludeDifferent
#$CertSame=Compare-Object -ReferenceObject $CertAll -Property ThumbPrint -IncludeEqual -ExcludeDifferent
$CertSame | foreach{GCI -filter "" –path Cert:\LocalMachine\My\$($_.thumbprint)} | Select-Object -Property Issuer, #{n=’ExpireInDays’;e={($_.notafter – (Get-Date)).Days}} -First 1
Thank you to #bruce-zhang
Similar to #bruce-zhangs's excellent answer but gets the certs in use first, then retrieves only those from the appropriate certificate stores (instead of only looking at the My cert store):
Import-Module WebAdministration
$CertsInUse = Get-Childitem -Path IIS:\SslBindings
$CertsInUse | foreach{Get-Childitem –path Cert:\LocalMachine\$($_.Store)\$($_.Thumbprint)} | Select-Object -Property FriendlyName,Subject, #{n=’ExpireInDays’;e={($_.notafter – (Get-Date)).Days}}
Here it is with a more verbose foreach:
Import-Module WebAdministration
$CertsInUse = Get-Childitem -Path IIS:\SslBindings
$CertsDetails = #()
foreach ($Cert in $CertsInUse) {
$CertsDetails += Get-ChildItem -Path Cert:\LocalMachine\$($Cert.Store)\$($Cert.Thumbprint)
}
$CertsDetails | Select-Object -Property FriendlyName,Subject, #{n=’ExpireInDays’;e={($_.notafter – (Get-Date)).Days}}
#checkCertExpDate-manual.ps1
$day = Get-Date -Format yyyyMMdd
$threshold = 5000 #Number of days to look for expiring certificates
$deadline = (Get-Date).AddDays($threshold) #Set deadline date
Dir Cert:\LocalMachine\My | foreach {
If ($_.NotAfter -le $deadline) { $_ | Select Issuer, Subject, NotAfter, #{Label="Expires In (Days)";Expression={($_.NotAfter - (Get-Date)).Days}} }
}
Then you just grep for the name:
.\checkCertExpDate-manual.ps1|Select-String -pattern "companyname"
Now, I can set the '$threshold' to whatever I want...
I invoke this remotely, after I copied to every server, and wrote the output to a log I then email to myself automatically every week from a scheduled task.
#D:\batch\checkCertExpDate.ps1
$day = Get-Date -Format yyyyMMdd
Set-Location d:\batch
$serverlist = gc ".\serverlist.txt"
foreach($server in $serverlist)
{
$threshold = 45 #Number of days to look for expiring certificates
$deadline = (Get-Date).AddDays($threshold) #Set deadline date
Invoke-Command $server { Dir Cert:\LocalMachine\My } | foreach {
If ($_.NotAfter -le $deadline) { $_ | Select Issuer, Subject, NotAfter, #{Label="Expires In (Days)";Expression={($_.NotAfter - (Get-Date)).Days}} }
}|select -expandproperty Subject|out-file .\output\$day-ExpiringIISSSLCerts.txt -Encoding ascii -Append
}
# Start mail send
$log = "d:\batch\output\$day-ExpiringIISSSLCerts.txt"
if(Test-Path -Path $log){
$smtpServer = "smtp.domain.com"
$messageSubject = "Verify SSL Cert Check Report - " + $env:computername
$message = New-Object System.Net.Mail.MailMessage
$message.From = "authorizedaccount#domain.com"
$message.To.Add("patrick.burwell#domain.com")
$message.Subject = $messageSubject
$message.IsBodyHTML = $true
$message.Body = "<head><pre>$style</pre></head>"
$message.Body += "Cert Check Report - " + $env:computername
$message.Body += Get-Date
$message.Body += "<br><b>Expiring Non-Prod Verify SSL Certificates Report from " + $env:computername + "</b>"
$message.Attachments.Add($log)
$smtp = New-Object Net.Mail.SmtpClient($smtpServer)
$smtp.Send($message)
}
$result = Get-content $log
write-host $result |format-list -View table

Using Powershell Get-ItemProperty through all of AD computers object

I'm a complete newbie in Powershell (and programming as you may have guessed), I want to get the result of the following PS command for each of our AD computer object and print the result in a text file...but I'm completely lost. Does anyone have a lifeline I could hold on to?
Get-ItemProperty "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\*" | Select-String -Pattern "mysoftwarename"
Thank you very much.
$ScriptBlock = {Get-ItemProperty "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\*" | Select-String -Pattern "mysoftwarename"}
$Computers = (Get-ADComputers -filter * ).name
$Creds = (Get-Credential)
foreach ($Computer in $Computers)
{
"`n`n$Computer`n" >> .\file.txt # "`n" just emulates Enter key press
Invoke-Command -ComputerName $Computer -ScriptBlock $ScriptBlock -Credential $Creds >> .\file.txt
}
This will work fine if you have all your computers online and PS remoting configured properly. Otherwise, it will require modifications.

Powershell retrieving cert by Thumbprint as string versus string variable

I'm trying to piece together some PowerShell code to loop through a list of servers, return some info regarding their IIS sites and bindings, and if they have an https binding, get the certificateHash and use that locate the cert by thumbprint and return its expiration date.
The problem I am having is, when i run my code below $binding.cerficateHash seems to return what I would expect, a string of the cert Hash, but when I use that certificateHash property to try and get the cert by its thumbprint, it doesnt work... but when I take the raw string value of the certificateHash value and hardcode it, it works...
I've inspected the certificateHash.GetType() and it appears to be just a string, so i dont understand what im doing wrong, and ive tried a handful of things, with no avail, granted this is my first crack at powershell so there's lots I don't know.
$sites = Invoke-Command -ComputerName $serverName { Import-Module WebAdministration; Get-ChildItem -path IIS:\Sites } -ErrorAction SilentlyContinue
foreach($site in $sites)
{
$serverName
$site.name
$site.physicalPath
foreach($binding in $site.bindings.Collection)
{
$binding.protocol
$binding.bindingInformation
$binding.certificateHash
$binding.certificateStoreName
if($binding.certificateHash)
{
# This outputs AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
$binding.certificateHash
# this retrieves a cert and returns its expiration date, Woohooo!
Start-Job Invoke-Command -ComputerName $serverName -ScriptBlock { (Get-ChildItem -path Cert:\LocalMachine\WebHosting | Where-Object {$_.Thumbprint -eq "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" })[0].GetExpirationDateString() }
# this does not find a cert, and ive tried many things, and no dice.
Start-Job Invoke-Command -ComputerName $serverName -ScriptBlock { (Get-ChildItem -path Cert:\LocalMachine\WebHosting | Where-Object {$_.Thumbprint -eq $binding.certificateHash })[0].GetExpirationDateString() }
# i've tried extracting the hash via "tostring" and using that, no dice
$hash = $binding.certificateHash.ToString()
Start-Job Invoke-Command -ComputerName $serverName -ScriptBlock { (Get-ChildItem -path Cert:\LocalMachine\WebHosting | Where-Object {$_.Thumbprint -eq $hash })[0].GetExpirationDateString() }
# i've tried adding some wildcards and using the -like operator, no dice.
$hash = "*" + $binding.certificateHash + "*"
Start-Job Invoke-Command -ComputerName $serverName -ScriptBlock { (Get-ChildItem -path Cert:\LocalMachine\WebHosting | Where-Object {$_.Thumbprint -lilke $hash })[0].GetExpirationDateString() }
}
}
}
Example output for a site.
Site1
D:\Apps\site1
http
*:80:Site1-test.ourdomain.com
https
*:443:Site1-test.ourdomain.com
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
WebHosting
The computer you invoke the script block on doesn't know about the $binding variable in your local session. (That's also why it works when passing a literal string.)
Try passing the value as argument:
Invoke-Command -Computer $serverName -Script {
param ($hash)
(gci Cert:\LocalMachine\WebHosting | ? Thumbprint -eq $hash)[0].GetExpirationDateString()
} -Arg $binding.certificateHash

Get-content Pipe to Select String and retrieve ComputerName

I'm trying to write a script that I can look at a list of remote servers, search for certain file type. Then if the File consists with a certain string i want to know what file that is and on what computer.
I started with the basics but when I do Select-String I can't retrieve the file nor can i find the computer name, it just outputs the same string as i have in my script.
I'm sure i'm missing something basic but any suggestions here will greatly be appreciated.
$servers = ArrayofServers
ForEach ($server in $Servers){
invoke-command -computername $server {Get-ChildItem "D:\Folder\Location\Windows\" -Include *.txt -Recurse |
Get-Content |
Select-String 'String to Select' |
Out-String
} -Credential $credential }
I want to be able to Select FileName,Pscomputername and Select-String.
I find it easier to break the pipeline. I have tested this code successfully:
$Servers = #("ArrayofServers")
foreach ($Server in $Servers)
{
Invoke-Command -ComputerName $Server -ScriptBlock {
$Results = $false
$SearchResults = Get-ChildItem "D:\Folder\Location\Windows\" -Include *.txt -Recurse
foreach ($SearchResult in $SearchResults)
{
If ($SearchResult | Get-Content | Select-String 'String to Select')
{
$SearchResult.FullName
$SearchResult | Get-Content
$Results = $true
}
}
If ($Results)
{
$env:COMPUTERNAME
}
} -Credential $Credential
}

Detecting files on a remote server

Guessing remote registry isn't available (hardened builds so service isn't running) - I can't query the registry for a specific value. However a file is present on the server I am analysing which provides the data I need. Thus far I have written the following - I would appreciate if this can be reviewed as it just hangs - I'm guessing that I would benefit from a if exists statement for the parent directory..
Suggestions and help very much appreciated (only been using PowerShell for a short time so working hard to get to grips with this.
Set-ExecutionPolicy RemoteSigned -ErrorAction SilentlyContinue
$servers = Get-Content -Path C:\Windows\System32\list3.txt
$out = ForEach ($server in $servers)
{
invoke-command -computername $server {Get-ChildItem -Path "C:\ProgramData\Microsoft\Microsoft Antimalware\Definition Updates\" -Exclude Backup -Filter mpavdlta.vdm -Recurse | Select-Object -Last 1 | Select LastWriteTime | ft -AutoSize}
}
$out|Out-File C:\Temp\Versions.log
$servers = Get-Content -Path "X:\ServerList.txt"
$logfile = "X:\Versions.log"
$Include = "Include.file"
$out = ForEach ($server in $servers)
{
Write-Output(Get-ChildItem -Path "\\$Server\X$\Remote Folder\Structure\" -Exclude Backup -Filter $Include -Recurse | Select-Object -Last 1 | Select LastWriteTime | ft -AutoSize) | Out-File $logFile
}
$out
Are you using account that has privileges on remote machine. If so this should provide a path to go down. This will pull server name from list and interrogate via \UNC\admin$ share. Serverlist.txt was just a list of machines in the following format.
machinename.domain.com
I had a look at your original request. Can you not loop through the serverlist and start the remote reg service, do your job and then stop it.
Something like.
$servers = Get-Content -Path "X:\ServerList.txt"
$Service = "Remote Registry"
$out = ForEach ($server in $servers)
{
Get-Service -Name $Service -ComputerName $Server | Set-Service -Status Running
Do remote reg stuff
Get-Service -Name $Service -ComputerName $Server | Set-Service -Status Stopped
}
$out
https://technet.microsoft.com/en-us/library/hh849849.aspx
Your script works, but it might be hanging due to your query (sounds like its capturing too many items). Does the ".vdm" file reside on that exact directory? You can remove the -Recurse if it does. Here's a modified version of yours. I just added the connection and destination checks.
Set-ExecutionPolicy RemoteSigned -ErrorAction SilentlyContinue
$servers = Get-Content -Path C:\Windows\System32\list3.txt
$out = ForEach ($server in $servers)
{
If(Test-Connection $server -Quiet){
Invoke-Command -Computername $server {
param([string]$parentPath)
If(Test-Path $parentPath)
{
#Write-Host "$parentPath Exists on $env:COMPUTERNAME."
Get-ChildItem -Path $parentPath -Exclude Backup -Filter mpavdlta.vdm -Recurse | Select-Object -Last 1 | Select LastWriteTime | ft -AutoSize
}
Else{
#Write-Host "$parentPath does not exist on $env:COMPUTERNAME"
}
} -ArgumentList $parentPath
}
Else { Write-host "Unable to connect to $server." }
}
$out | Out-File C:\Temp\Versions.log