Get hard drives SMART data with email notifications using PowerShell - windows

if
I want to use internal Windows resources to follow server disks health. So, I would like to schedule a PowerShell script on several servers. The script should take each server hard drives SMART status and send appropriate emails (through my internal SMTP server) to me. Some servers could contain more than one HDD.
My initial script is Ok and works fine:
$emailto="admin#example.com"
$emailfrom="$env:COMPUTERNAME#example.com "
$emailserver="smtp.example.com "
$output = Get-WmiObject -query "Select * from Win32_diskdrive" | select Model, Status | out-string
function send-email
{
$EmailSubj = "Disks SMART report on $env:COMPUTERNAME"
$EmailBody = $output
Send-MailMessage -To "$emailto" `
-From "$emailfrom" `
-Subject "$EmailSubj" `
-Body "$EmailBody" `
-smtpServer "$emailserver"
}
send-email
Typical output is something like this:
Model Status
----- ------
ST2000NC000 OK
ST2000NC000 OK
ST2000NC000 OK
ST2000NC000 OK
The question is how to avoid a lot of spam and email only if BAD statuses available? I would like to filter Get-WmiObject results in order to get the mails ONLY if at least one HDD status will not be "OK". I am not sure but there should be also "Degraded" and "Pred Fail" statuses. I tried different foreach and If/Else combinations, but I cannot find method how to force PowerShell check "OK" string from the Status property (and also for each HDD) to get my goal. Can someone help me, please?

If you lose the out-string, you will have access to the object properties and will be able to filter on status not being OK.
But just a thought, I would rather have lots of emails as that way I would know the script is running or at least an email saying server 1 has no bad sectors etc...
$output = Get-WmiObject -query "Select * from Win32_diskdrive" | select Model, Status | where status -ne "OK"

$output = Get-WmiObject -query "Select * from Win32_diskdrive" | select Model, Status
| where-object {$_.Status -ne "OK"} | Out-String
Outputs drives if their status is not equal to "OK". This should account for any other status options, including "BAD".

Related

Trying to write a powershell script that shows all locked files with computer names instead of IP address

The task given was to create a way for our staff to see who has the file open that they want to use, as Windows says it is either locked and doesn't name the person who has it locked, or it displays the person who made the file but not the person who currently has it open.
I can look it up in Computer Management on the fileserver, but were are hoping to speed up this for the end users.
I've written this powershell script on our fileserver and it works perfectly, I have this running every 5 minutes in Task Scheduler with administrative permissions:
get-smbopenfile -ClientUserName * |select clientcomputername,clientusername,path | Out-File -Encoding utf8 "S:\LockedFiles.txt" -width 300
The output looks like this:
clientcomputername clientusername path
------------------ -------------- ----
IPADDRESS DOMAIN\USERNAME S:\FOLDER\FILE.FILEEXTENSION
What I really want to do now is get the computer name rather than the IP address, just in case staff are logged into multiple machines at the same time.
I wondered if ClusterNodeName or PSComputerName would provide this, but the returned data is always blank.
I thought about this and below is one option (the first line is pseudocode), but as I see it that would mean recursively altering the piped data or reading in piped data, which I'm not even sure how to do.
$ipaddress = IPADDRESS
$Workstation = [System.Net.Dns]::GetHostByName($ipaddress)
Write-Host $Workstation.HostName
Anyone have any ideas on how I can do this? Is there a better way?
I assume you're looking to add a new property to your output object that has the resolved DNS Name from the IP Address found in the ClientComputerName property. For this you use Resolve-DnsName to attempt the name resolution and a Try Catch in case it fails to capture the exception message. For the export I would recommend you to use Export-Csv.
Get-SmbOpenFile -ClientUserName * | ForEach-Object {
$dnsName = try {
(Resolve-DnsName $_.ClientComputerName -ErrorAction Stop).NameHost
}
catch {
[ComponentModel.Win32Exception]::new($_.Exception.NativeErrorCode).Message
}
[pscustomobject]#{
ClientIpAddress = $_.ClientComputerName
ResolvedHostName = $dnsName
ClientUserName = $_.ClientUserName
Path = $_.Path
}
} | Export-Csv "S:\LockedFiles.csv" -Encoding utf8 -NoTypeInformation

Adding values from multiple computers with custom headers in a single csv

I need to accomplish the scenario below and for that I have to create a couple of powershell scripts to accomplish it.
The environment: Windows servers and Windows clients
Scenario
1- Create a script to be run in a specific time every day (with Task Scheduler) on windows clients. This script will push the current computer's hostname and IP address to a csv file with a specific headers (let's call these "Hostnames" and "IP Address"). These header shouldn't be changed as these scripts run from multiple computers at that time and all computers' data should be appended to each header, not overwrite them as this operation continues.
2- From a server, (after 15 mins) as fetching this "computer" list (csv), there should be a ping check for each of them using their IP addresses. If pings are successful on these remote computers, it should say "This computer is up" next to the each computer name and its IP address. If pings are unsuccessful, it should say "This computer is down" next to the each computer name and its IP address. Above these "status" information, there should be another header (let's say "IsAlive"). This header should be added to the csv as well.
So with this setup, I could be able to learn which computers are UP at a specific time and after I trigger some actions them, I could be able to learn if they're still up or down.
To be honest, I couldn't take a long way for it. I started to write a script about the first step but I couldn't combine the headers with values adding under them.
$header="Hostname"
$outputfile="\\10.10.10.40\reports\upcomputers.csv"
Add-Content $outputfile -Value $header
$hostname >> $outputfile
If I use this script (even if with one header), it's adding "NULL" after each alphabet of hostname and it doesn't append the other hostname under the "Hostname" header.
Additionally, I have no idea where to start adding the third header (IsAlive) and add each Test-NetConnection query's output as checking their IP addresses. I request you to show me a way to start with this section as well.
Finally, my output should be like that;
For the first step;
For the second step;
Thank you for your help and information
Stated on the main body of the request
Stage 2:
The easy way of doing this using PSCustomobject. Please find the sample code below:
$outputcsv = "C:\PowerShell\pingstatus.csv"
$hostlist = import-csv "C:\PowerShell\hostlist.csv"
$result = foreach($line in $hostlist){
$pingtest = Test-Connection -ComputerName $line.Hostname -Quiet -Count 1 -ErrorAction SilentlyContinue
if($pingtest) {
$OutputMessage = "This computer is up"
}
else {
$OutputMessage = "This computer is down"
}
[pscustomobject][ordered]#{
HostName = $line.Hostname
IPAddress = $line.IPaddress
IsAlive = $OutputMessage
}
}
$result | Export-csv -Path $outputcsv -NoTypeInformation
The Hostname and IPAddress input will be taken as input.
Note: Your input csv file should contain Hostname IPaddress as header.
Stage1:
why not?
$outputfile="\\10.10.10.40\reports\upcomputers.csv"
$serverDetails = [PSCustomObject]#{
Hostname = "$env:COMPUTERNAME"
IPAddress = (Get-WmiObject -Class Win32_NetworkAdapterConfiguration | where {$_.DHCPEnabled -ne $null -and $_.DefaultIPGateway -ne $null}).IPAddress | Select-Object -First 1
}
$serverDetails | Export-csv $outputfile -Append -NoTypeInformation
There are multiple ways to get IP address a computer, I used Get-WMIObject. You may use other simple ways like Test-Connection or Get-NetIPAddress.
To learn more: Please see
[PsCustomObject]: https://learn.microsoft.com/en-us/powershell/scripting/learn/deep-dives/everything-about-pscustomobject?view=powershell-7.3

Receive-Job different than direct call

Needed to change my code from direct call to Start-Job precedure because of timeouts caused by Symantec Antivirus (SEP) v14, making my script hanging.
this snap was working fine so long:
$updateDaten = Get-HotFix -computername "myserver" | % { $_.psbase.properties["installedOn"].Value } | Group-Object | select-object Name
The change to a start-job preocedure completely messes up the resulting object. im googling and testing for two days now but cannot find out what's so complicatet in dealing with job objects:
function Get-HotfixesListAsJob($computer, $timeout){
$job = Start-Job { param($c) Get-HotFix -computername $c |
% { $_.psbase.properties["installedOn"].Value } | Group-Object
} -ArgumentList $computer
Wait-Job $job -Timeout $timeout
Stop-Job $job
Receive-Job $job
Remove-Job $job
}
$updateDaten = Get-HotfixesListAsJob -computer "myserver" -timeout 80
However I am not able to get the same result back using Receive-Job. I Always get some wired job-object and I am not able to just extract the data as it was before. Why is the result so completely different? is it possible to get just the data back in an object as it was before instead of a job object?
btw. this interesting article did not solve my problem:
https://learn-powershell.net/2014/06/27/quick-hits-did-i-really-lose-my-output-with-receive-job-by-not-usingkeep/
Thanks for your answers in advance
The issue what you are having is not because of the code. Its because Start-job or any PS Job session, basically, will have all the details, properties related to that job.
So, I can see in the first case you have done a grouping and finally selecting only the name which is not there in the later case.
So I would suggest you to add that to the Function also.
One more thing , Wait-job will also output the value in the console. Either redirect or pipe it to null in order to avoid that.
You can also receive the job using receive-job with the job id or the name. I think that will be better. Just check the status of the job; Once done receive it.

WMIMethodException with .InstallProductKey

First off, this is my first post, so if I incorrectly posted this in the wrong location, please let me know.
So, what we're trying to accomplish is building a powershell script that we can throw on our workstation image so that once our Windows 10 boxes are done imaging, that we can click on a powershell script, have it pull the key from the BIOS, and automagically activate it. That being said, here is the script that we've put together from various sources.
(Get-WmiObject -query ‘select * from SoftwareLicensingService’).OA3xOriginalProductKey | out-file c:\license.txt
$computer = gc env:computername
$key = get-content c:\license.txt
$service = get-wmiObject -query “select * from SoftwareLicensingService” -computername $computer
$service.InstallProductKey($key) <--------THIS IS WHERE IT FAILS
$service.RefreshLicenseStatus()
We start running into the issues on the line $service.InstallProductKey($key). It seems, that no matter how we try to invoke that, it will consistently fail with the error "Exception calling "InstallProductKey"". I've even replaced the variable ($key) with the specific activation key, and it STILL fails with the same error.
The reason we have it outputting to a license txt file part way through is so that we can verify that the command is indeed pulling the product key (which it is).
At this point, I'm not sure where to go. It seems that people have tried to do this before, however, nobody has really wrapped up their posting with what worked and/or what didn't. I can't imagine that this is impossible, but I'm also not fond of wasting anymore time than needed, so anybody that has any insight into this issue, I'd be very grateful.
We've gotten it to work on two machines that were previously activated, and later deactivated, but on new machines that have been freshly imaged, and have yet to be activated, it will fail every time.
Two things as per my observation:
(Get-WmiObject -query ‘select * from SoftwareLicensingService’).OA3xOriginalProductKey | out-file c:\license.txt
I don't think that it is returning any value to your license.txt.
If yes, then I would like you to see if there is any space before and after the license key. You can use trim during getting the content from the file.
Second thing, when you are getting the content from the file make sure it is not separating into multiple lines. In that case, you have to cast it as string like [String]$key or you can call toString() method for this.
One more important thing is to refresh after the installation.
$service.RefreshLicenseStatus()
Note: Make sure you are running the shell in elevated mode.
Alternative: Try Hardcoding the values and see the result
$key = "XXXXX-XXXXX-XXXXX-XXXXX-XXXXX" # hardcode the key
$computer= "Computer01" # Hardcode the computer
$service = get-wmiObject -query "select * from SoftwareLicensingService" -computername $computer
$service.InstallProductKey($key)
$service.RefreshLicenseStatus()
For further thing ,please post the exact error.
Hope it helps...!!!
Found out that the key from Get-WmiObject has whitespace on the end. The original command will work if a .Trim() is added. Also not running as administrator will result in the same error.
(Get-WmiObject -query ‘select * from SoftwareLicensingService’).OA3xOriginalProductKey | out-file c:\license.txt
$computer = gc env:computername
$key = (get-content c:\license.txt).Trim() #trim here
$service = get-wmiObject -query “select * from SoftwareLicensingService” -computername $computer
$service.InstallProductKey($key)
$service.RefreshLicenseStatus()

Setting counter on Windows Event log for Email notification

I am having below script:
$pattern = 'Unable to authenticate user!'
$events = Get-WinEvent -ea SilentlyContinue `
-ProviderName "Windows DB Controller - Task Manager Service"|
Where-Object { $_.TimeCreated -gt [datetime]::today -and $_.Message -match $pattern }
$events >> D:\Error.txt
if ($events) {
Send-MailMessage -SmtpServer smtp.domain.com -From No-reply#domain.com -To sunny#domain.com -Subject 'Error found in log' -Body $events
}
I had scheduled it to run on every 10 mins and purposely ,I wanted to achieve following point using above script:
Search the specified error message in the event viewer log only for current-date and as soon as the error message encountered send a email notification to me but didn't want to receive email notification for the error message which appreared today and for which I had already been notified (I mean , wanted to receive error-notification only once for a specific time of current day).
But problem I am facing here is: Getting multiple notifications for same error message for which already being notified.
I hope I am clear enough to put my exact problem.
Could you please help me out, how to resolve this problem ?
If you are running the script every 10 minutes, I would change the condition on the Where-Object so instead of getting all of the events that are "today"; I would change it to get only the events that happened in the last 10 minutes. i.e. the code becomes:
Where-Object { $_.TimeCreated -gt [datetime]::now.AddMinutes(-10) -and $_.Message -match $pattern }
Have a look at this thread:
Powershell - Tail Windows Event Log? Is it possible?
It's on tailing an event log, but the same method should work for what you're tyring to do. Just save the last index number to a file between runs.
How about the following approach:
Register-WmiEvent -Query "select * from __InstanceCreationEvent where TargetInstance ISA 'Win32_NTLogEvent' and TargetInstance.SourceName = 'Windows DB Controller - Task Manager Service' and TargetInstance.Message LIKE '%Unable to authenticate user!%'" -SourceIdentifier "MyEventListener" -Action {
#Your actions
write-host "$($eventargs.NewEvent.TargetInstance.RecordNumber) at $($eventargs.NewEvent.TargetInstance.TimeGenerated)"
}
It uses WMI to subscribe to the event that occurs when an eventlog entry is generated with your criterias. The action itself will only return the new object(so no more duplicates). I've included a sample action to help you understand how to access the object. This method will give you live monitoring.
Inside the action, $eventargs.NewEvent.TargetInstance will give you the object which is an instance of win32_ntlogevent. To see properties of this class, check out TechNet or run the following command:
([wmiclass]'win32_ntlogevent').Properties | ft Name
To make the script run forever, just call your script with powershell -file script.ps1 -noexit or include a while($true) loop at the end of your script. (I'm not sure how the while-loop will affect resource usage longterm, you'd have to test).

Resources