Is there a way to email specific powershell results - powershell

Test-Connection -ComputerName (Get-Content 'C:\xxxxx\xxxxx.txt') -ErrorAction Continue -Count 1
I currently use the above command to test connections on sevreal remote PCs. It's basic and does the job, however i am looking to automate this and where i get stuck is how to filter only the failed connections from the result and output them as an email?
Thanks
really unsure how to go from here.

You could do something like this:
$unreachables = Get-Content 'C:\xxxxx\xxxxx.txt' | ForEach-Object {
if (-not (Test-Connection -ComputerName $_ -Count 1 -Quiet)) {
# output the failed machines
$_
}
}
if (#($unreachables).Count) {
# create a Hashtable with parameters used for splatting to Send-MailMessage
# something like this
$mailParams = #{
From = 'me#contoso.com'
To = 'mycolleague#contoso.com'
Subject = "unreachable computers"
SmtpServer = 'mail.contoso.com'
Body = "The following computers are off-line ({0}):`r`n`r`n{1}" -f (Get-Date), ($unreachables -join "`r`n")
}
Send-MailMessage #mailParams
}
else {
Write-Host 'All tested computers are online'
}

Related

Powershell return variable from within a Invoke-Command

I'm developing a powershell script (and kind of new to it) to go to a couple of servers and extract the RDP logons, so we can check if a certain policy is being followed.
So I've search a bit and now I got the script output exatcly as I want. But now I want to send the result over email.
But I have a problem, because the variable which is output to console (with the info I need) is inside a Invoke-Command, so I cannot use it after outside the Invoke-Command to send the email.
This is my code:
$ServersToCheck = Get-Content "C:\Temp\Servers-RDP2.txt"
foreach ($server in $ServersToCheck) {
Invoke-Command -ComputerName $server -ErrorAction SilentlyContinue {
$username = "user"
$FilterPath = "<QueryList><Query Id='0'><Select Path='Microsoft-Windows-TerminalServices-RemoteConnectionManager/Operational'>*[System[(EventID=1149) and TimeCreated[timediff(#SystemTime) <= 604800000]]] and *[UserData[EventXML[#xmlns='Event_NS'][Param1='{0}']]]</Select></Query></QueryList>" -f $username
$RDPAuths = Get-WinEvent -ErrorAction SilentlyContinue -LogName 'Microsoft-Windows-TerminalServices-RemoteConnectionManager/Operational' -FilterXPath $FilterPath
[xml[]]$xml = $RDPAuths | Foreach { $_.ToXml() }
$EventData = Foreach ($event in $xml.Event) {
New-Object PSObject -Property #{
TimeCreated = (Get-Date ($event.System.TimeCreated.SystemTime) -Format 'dd-MM-yyyy hh:mm:ss')
User = $event.UserData.EventXML.Param1
Domain = $event.UserData.EventXML.Param2
Client = $event.UserData.EventXML.Param3
Server = hostname
}
}
$EventData | FT
}
}
So, I need to use $EventData outside the Invoke-Command so I can add the results of all servers and then send it over by email.
How can I use that variable outside the Invoke-Command?
Thanks

Sending email with CSV from a Running Process check

I'm trying to check if a process is running on a remote computer (Eventually will be about 100 computers). If the process is not running, I'd like it to put the computername/IP into a CSV and then email that out. If the process is running on all machines, I'd like the script to not send an email out at all. To do this, I'd like to test the machines first to check they're online (If they're offline, we've either got bigger problems or it's off for a reason, but that's not what this process is checking for.
I'm going to be testing this script on a few machines with just the notepad process at the moment as it's something I can do on a test machines reletively quickly.
I'm a little stuck at the moment, because I don't know how to get the results from the process check to be put into a CSV and then emailed. In the code snippet below, it's not generating the outfile, but have left the variable I was testing with and the path to where the attachment would be in the send-mailmessage. Any advice will be appreciated, I'm still learning powershell at the moment so don't know all the tricks and tips yet.
Cheers
# Mail server Configuration
$MailServer = "mail.server.co.uk"
$MailFrom = MailFrom#server.co.uk"
# Mail Content Configuration
$MailTo = "Recipient#Server.co.uk"
$MailSubjectFail = "INS Process not running on $DAU"
$MailBodyFail = "The INS Process on the DAU $DAU is not running. Please manually start process on DAU $DAU"
# Process Info
$Process = "Notepad"
$ProcessIsRunning = { Get-Process $Process -ErrorAction SilentlyContinue }
#Results Info
$Exportto = "C:\Scripts\Content\INSChecker\Results.csv"
# Get DAU Information
foreach($line in (Get-Content C:\Scripts\Content\INSChecker\INSList.cfg)){
$line = $line.split(",")
$DAU = $line[0]
$DAUIP = $line[1]
# Test Connection to INS DAU
write-host "Testing: $DAU / $DAUIP"
$TestDAU = Test-Connection $DAU -quiet
$TestDAUIP = Test-Connection $DAUIP -quiet
write-host "Tests: $TestDAU / $TestDAUIP"
If($TestDAU -ne 'True'){
If($TestDAUIP -ne 'True'){
write-host "DNS Not resolved for $DAU"
Write-Output "INS $DAU/$DAUIP is OFFLINE" | Out-File C:\Scripts\Content\INSChecker\INSProcessCheck.log -append
}
}
Else{
# Get Process Running State and Send Email
if(!$ProcessIsRunning.Invoke()) {
Send-MailMessage -To $MailTo -From $MailFrom -SmtpServer $MailServer -Subject $MailSubjectFail -Body $MailBodyFail -Attachments C:\Scripts\Content\INSChecker\Results.csv
} else {
"Running"
}
}
}
Hopefully this gives a you a hint on where to begin and how to approach the problem, I have removed the irrelevant parts of the script and only left the logic I would personally follow.
The result of $report should be an object[] (object array) which should be very easy to manipulate and very easy to export to CSV:
#($report).where({ $_.SendMail }) | Export-Csv $exportTo -NoTypeInformation
I'll leave you the remaining tasks (attach the CSV, send the emails, etc) for your own research and design.
$ErrorActionPreference = 'Stop'
# Process Info
$Process = "Notepad"
$ProcessIsRunning = {
param($computer, $process)
# On Windows PowerShell -ComputerName is an option,
# this was removed on PS Core
try
{
$null = Get-Process $process -ComputerName $computer
# If process is running return 'Running'
'Running'
}
catch
{
# else return 'Not Running'
'Not Running'
# send a Warning to the console to understand why did this
# fail ( couldn't connect or the process is not running? )
Write-Warning $_.Exception.Message
}
}
#Results Info
$ExportTo = "C:\Scripts\Content\INSChecker\Results.csv"
$exportProps = 'Server', 'IP', 'Ping', 'DNSResolution', 'Process', 'SendMail'
# Get DAU Information
$report = foreach($line in Get-Content path/to/file.txt)
{
$status = [ordered]#{} | Select-Object $exportProps
$DAU, $DAUIP = $line = $line.split(",")
$status.SendMail = $false
$status.Server = $DAU
$status.IP = $DAUIP
# Test ICMP Echo Request and DNS Resolution
$ping = Test-Connection $DAUIP -Quiet
$dns = Test-Connection $DAU -Quiet
$status.Ping = ('Failed', 'Success')[$ping]
$status.DNSResolution = ('Failed', 'Success')[$dns]
$status.Process = & $ProcessIsRunning -computer $DAUIP -process $Process
if(-not $ping -or -not $dns -or $status.Process -eq 'Not Running')
{
$status.SendMail = $true
}
[pscustomobject]$status
}
#($report).where({ $_.SendMail }) # => This is what should be mailed

Not getting remote user names with (Get-WmiObject -Class win32_computersystem -ComputerName $computer).UserName

First and foremost thanks for your time and help.
My issue here is that I am having an erratic behavior in getting the name of the remote logged users. I want to send a message to a certain list of computers or IPs, and if somebody is logged get who saw the message.
To do so I created a script which should read and get the computer names one by one, and see if is connected. If so, get the name of the user, send the message and then show in table and write on a txt file the results:
#We read the "Lista" file with IPs or computer names, works with both
$PCLIST = Get-Content 'C:\Users\XXXX\Desktop\Lista.txt'
#Add the date tot he txt files to be created
$Fecha = date
echo $Fecha > "C:\Users\XXXX\Desktop\ListOffline.txt"
echo $Fecha > "C:\Users\XXXX\Desktop\Done.txt"
#We check every computer and if ONLINE send the message
foreach ($computer in $PCLIST)
{
if ((Test-NetConnection -ComputerName $computer -WarningAction SilentlyContinue).PingSucceeded -eq $true) #If ping back is succesfull then write the message
{
$Usuario = (Get-WmiObject -Class win32_computersystem -ComputerName $computer).UserName
if ((Get-WmiObject -Class win32_computersystem -ComputerName $computer).UserName) {" "} else {$Usuario = "Usuario Remoto"}
$output = #{ 'Computer_Name / IP' = $computer }
$output.Usuario_Conectado = (Get-WmiObject -Class win32_computersystem -ComputerName $computer).UserName
msg * /server:$computer "Hey!!" $Usuario ", Something meaninfull :) " #Message1
msg * /server:$computer "And something more meaningfull even ;)" #Message2
echo "$computer, avisado y recibido por $Usuario" >> "C:\Users\XXXX\Desktop\Done.txt"
}
else
{
$output = #{'Computer_Name / IP' = $computer }
$output.Usuario_Conectado = "OFFLINE"
echo $Computer >> "C:\Users\XXXX\Desktop\ListOffline.txt"
}
[PSCustomObject]$output
}
The message part works as expected and the computers, when logged, can see the messages BUT:
I can't get the names of the remote logged users on the computer's list, I get mine and the rest as empty names. In some weird variation I could, but can't find what the problem is anymore. As pre-requisites I allowed Win RM and firewall config:
I went to the computers where this will be sent and modified the registry: "reg add "HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Terminal Server" /v "AllowRemoteRPC" /t "REG_DWORD" /d "1" /f"
and opened the sshnet to allow remote access to get the names: "netsh firewall set service remoteadmin enable"
In the image in the orange circles should go the remote users logged but I get empty names
I want to send the message IF the user is logged, as remote user or on the console, now the txt file of "Done" gets written even when the user don't receive any message...
So, there should be an issue on this call: "(Get-WmiObject -Class win32_computersystem -ComputerName $computer).UserName" to get the names, this worked but I can't get the names on the remote logged users computers anymore... any idea or suggestion? I can accept get those in other form if is available and easier.
Thanks and best,
Juan
If you're using command line utilities anyway, why not use quser as its designed to get the information you need:
Function FindLogons {
Param (
[string]$Computer
)
Try {
$Query = quser /server:$Computer 2>&1
If ($LASTEXITCODE -ne 0) {
$ErrorDetail = $Query.Exception.Message
Switch -Wildcard ($Query) {
'*[1722]*' {
$Status = 'Remote RPC not enabled'
}
'*[5]*' {
$Status = 'Access denied'
}
'No User exists for*' {
$Status = 'No logged on users found'
}
default {
$Status = 'Error'
}
}
$result = [pscustomobject]#{ErrorStatus = $Status;ErrorMessage = $ErrorDetail}
$result.PSObject.Properties | ForEach-Object {
Write-Host $_.Name "`t" $_.Value
}
Return
}
Else {
Write-Host "Query complete"
}
$Query | Select-Object -Skip 1 -ErrorAction Stop | ForEach-Object {
$CurrentLine = $_.Trim() -Replace '\s+',' ' -Split '\s'
# If session is disconnected different fields will be selected
If ($CurrentLine[2] -eq 'Disc') {
$SearchResult = [pscustomobject]#{
UserName = $CurrentLine[0];
SessionName = $null;
Id = $CurrentLine[1];
State = $CurrentLine[2];
IdleTime = $CurrentLine[3];
LogonTime = $CurrentLine[4..($CurrentLine.GetUpperBound(0))] -join ' '
}
# LogonTime = $CurrentLine[4..6] -join ' ';
}
Else {
$SearchResult = [pscustomobject]#{
UserName = $CurrentLine[0];
SessionName = $CurrentLine[1];
Id = $CurrentLine[2];
State = $CurrentLine[3];
IdleTime = $CurrentLine[4];
LogonTime = $CurrentLine[5..($CurrentLine.GetUpperBound(0))] -join ' '
}
}
If ($SearchResult) {
$SearchResult
}
}
}
Catch {
Write-Host "Error: $_"
}
Write-Host "Done"
}

Try/Catch or If/Else?

Hello Guys im having trouble trying to figure out how to make this script to work, im very new on scripting but i do understand most of it but still figuring out some things.
try {
Test-Connection -Computername $_ -count 1 -ErrorAction Stop
} catch {
$_.Exception.ErrorCode -eq 0x800706ba
} `
{
$err = 'Unavailable (Host Offline or Firewall)'
}
try {
Test-UserCredentials -Username testuser -Password (Read-Host -AsSecureString)
} catch {
$_.CategoryInfo.Reason -eq 'UnauthorizedAccessException'
} `
{
$err = 'Access denied (Check User Permissions)'
}
Write-Warning "$computer- $err" | Out-File -FilePath c:\temp\Folder\Errors.txt -Append
What im looking for is for this script to test if the system responds or not. If True then next step would be to test credentials, and last would be to perform a get-wmiobject query. But if the system does not respond to ping then i want to catch the hostname that failed to respond ping, capture it and export it to a txt and do the same if the credential fails.
try..catch is for handling terminating errors. Don't abuse it for status checks by forcing a check to fail hard when it doesn't need to. If you just want to test the availability of a system run Test-Connection with the parameter -Quiet as an if condition:
if (Test-Connection -ComputerName $_ -Count 1 -Quiet) {
...
}
If you need to cascade multiple checks you could do so in a more readable manner by inverting the checks and returning with an appropriate message:
function Test-Multiple {
...
if (-not (Test-Connection -ComputerName $_ -Count 1 -Quiet)) {
return "Host $_ unavailable."
}
$pw = Read-Host -AsSecureString
if (-not (Test-UserCredentials -Username testuser -Password $pw)) {
return 'Login failed for user testuser.'
}
...
}
If you want the information about ping or login failures in log files you can just append it to the respective files:
function Test-Multiple {
...
if (-not (Test-Connection -ComputerName $_ -Count 1 -Quiet)) {
$_ | Add-Content 'C:\path\to\unavailable.log'
return
}
$pw = Read-Host -AsSecureString
if (-not (Test-UserCredentials -Username testuser -Password $pw)) {
$_ | Add-Content 'C:\path\to\login_failure.log'
return
}
...
}
Personally, I can't stand the behavior of Test-Connection. Throwing an exception when it doesn't successfully ping isn't the behavior I want. Like, ever. I understand why they did it that way, but it's not how I ever want a ping to work. Test-Path doesn't throw an exception when the path is invalid. It just returns false. Why is Test-Connection so unfriendly?
WMI allows you to capture the actual status code, and it also allows you to easily control the timeout so it will function much more quickly.
I tend to use this:
$Ping = Get-WmiObject -Class Win32_PingStatus -Filter "Address='$ComputerName' AND Timeout=1000";
if ($Ping.StatusCode -eq 0) {
# Success
}
else {
# Failure
}
If I actually want to decode the ping status code:
$StatusCodes = #{
[uint32]0 = 'Success';
[uint32]11001 = 'Buffer Too Small';
[uint32]11002 = 'Destination Net Unreachable';
[uint32]11003 = 'Destination Host Unreachable';
[uint32]11004 = 'Destination Protocol Unreachable';
[uint32]11005 = 'Destination Port Unreachable';
[uint32]11006 = 'No Resources';
[uint32]11007 = 'Bad Option';
[uint32]11008 = 'Hardware Error';
[uint32]11009 = 'Packet Too Big';
[uint32]11010 = 'Request Timed Out';
[uint32]11011 = 'Bad Request';
[uint32]11012 = 'Bad Route';
[uint32]11013 = 'TimeToLive Expired Transit';
[uint32]11014 = 'TimeToLive Expired Reassembly';
[uint32]11015 = 'Parameter Problem';
[uint32]11016 = 'Source Quench';
[uint32]11017 = 'Option Too Big';
[uint32]11018 = 'Bad Destination';
[uint32]11032 = 'Negotiating IPSEC';
[uint32]11050 = 'General Failure'
};
$Ping = Get-WmiObject -Class Win32_PingStatus -Filter "Address='$ComputerName' AND Timeout=1000"
$StatusCodes[$Ping.StatusCode];
You could do it like this:
if(Test-Connection -Computername $_ -Count 2 -ErrorAction 0 -Quiet) {
if(-not (Test-UserCredentials -Username testuser -Password (Read-Host -AsSecureString))) {
$err = "Access denied (Check User Permissions)"
}
} else {
$err = "Unavailable (Host Offline or Firewall)"
}
if($err) {
Write-Warning "$computer - $err" | Out-File -FilePath c:\temp\Folder\Errors.txt -Append
}
I believe Test-Connection and Test-Credentials are meant to return $true or $false rather than an exception (if properly used), so you don't really need try/catch here.

How can I automate Telnet port checking in Powershell?`

I'm currently trying to put together a script that queries AD for a list of computers, pings the computers to determine which ones are still active, and then telnets into a specific port on all the pingable computers. The output I'm looking for is a full list of pingable computers in AD for which I can't telnet to the said port.
I've read these few questions, but they don't quite hit on what I'm trying to do. I just want to see if the telnet connection is successful without entering telnet (or automate the quitting of telnet) and move on to the next machine to test. The AD and pinging portions of my script are set, I'm just stuck here. The things I've tried haven't quite worked as planned.
Here is the code for the first parts of the script, if needed:
Get-ADComputer -Filter * -SearchBase 'DC=hahaha,DC=hehehe' | ForEach {
$computerName = $_.Name
$props = #{
ComputerName = $computerName
Alive = $false
PortOpen = $false
}
If (Test-Connection -ComputerName $computerName -Count 1 -Quiet) {
$props.Alive = $true
}
Adapting this code into your own would be the easiest way. This code sample comes from the PowerShellAdmin wiki. Collect the computer and port you want to check. Then attempt to make a connection to that computer on each port using Net.Sockets.TcpClient.
foreach ($Computer in $ComputerName) {
foreach ($Port in $Ports) {
# Create a Net.Sockets.TcpClient object to use for
# checking for open TCP ports.
$Socket = New-Object Net.Sockets.TcpClient
# Suppress error messages
$ErrorActionPreference = 'SilentlyContinue'
# Try to connect
$Socket.Connect($Computer, $Port)
# Make error messages visible again
$ErrorActionPreference = 'Continue'
# Determine if we are connected.
if ($Socket.Connected) {
"${Computer}: Port $Port is open"
$Socket.Close()
}
else {
"${Computer}: Port $Port is closed or filtered"
}
# Apparently resetting the variable between iterations is necessary.
$Socket = $null
}
}
Here is a complete powershell script that will:
1. read the host and port details from CSV file
2. perform telnet test
3. write the output with the test status to another CSV file
checklist.csv
remoteHost,port
localhost,80
asdfadsf,83
localhost,135
telnet_test.ps1
$checklist = import-csv checklist.csv
$OutArray = #()
Import-Csv checklist.csv |`
ForEach-Object {
try {
$rh = $_.remoteHost
$p = $_.port
$socket = new-object System.Net.Sockets.TcpClient($rh, $p)
} catch [Exception] {
$myobj = "" | Select "remoteHost", "port", "status"
$myobj.remoteHost = $rh
$myobj.port = $p
$myobj.status = "failed"
Write-Host $myobj
$outarray += $myobj
$myobj = $null
return
}
$myobj = "" | Select "remoteHost", "port", "status"
$myobj.remoteHost = $rh
$myobj.port = $p
$myobj.status = "success"
Write-Host $myobj
$outarray += $myobj
$myobj = $null
return
}
$outarray | export-csv -path "result.csv" -NoTypeInformation
result.csv
"remoteHost","port","status"
"localhost","80","failed"
"asdfadsf","83","failed"
"localhost","135","success"