powershell Foreach-Object usage - powershell

I have script:
$servers = "server01", "s02", "s03"
foreach ($server in $servers) {
$server = (New-Object System.Net.NetworkInformation.Ping).send($servers)
if ($server.Status -eq "Success") {
Write-Host "$server is OK"
}
}
Error message:
An exception occured during a Ping request.
I need to ping each server in $servers array and display status. I think, that Foreach statement is not properly used, but I'm unable to find out where is the problem. Thank you for your advice

You should not be modifying the value of $server within the foreach loop. Declare a new variable (e.g. $result). Also, Ping.Send takes the individual server name, not an array of server names as an argument. The following code should work.
Finally, you will need to trap the PingException that will be thrown if the host is unreachable, or your script will print out a big red error along with the expected results.
$servers = "server1", "server2"
foreach ($server in $servers) {
& {
trap [System.Net.NetworkInformation.PingException] { continue; }
$result = (New-Object System.Net.NetworkInformation.Ping).send($server)
if ($result.Status -eq "Success") {
Write-Host "$server is OK"
}
else {
Write-Host "$server is NOT OK"
}
}
}

Related

Try Catch - How to Catch error but continue?

I've got this script, which loops through a foreach loop for each computer, and for each computer it loops through each NIC. Currently the Catch block is not running. What I want to do is catch the error (usually because get-wmi is not able to connect to a machine), do something (add some information to a PSCustomObject), but then continue to the next iteration. How do I catch an error but also continue the foreach loop?
param (
[Alias('Hostname')]
[string[]]$ComputerName = #('pc1','pc2'),
$OldDNSIP = '7.7.7.7',
$NewDNSIP = #('9.9.9.9','8.8.8.8')
)
$FailedArray = #()
$DHCPArray = #()
Foreach ($Computer in $ComputerName){
$NICList = Get-WmiObject Win32_NetworkAdapterConfiguration -computername $Computer | where{$_.IPEnabled -eq "TRUE"}
Foreach($NIC in $NICList){
If($NIC.DHCPEnabled -eq $false){
Try{
$DNSIPs = $NIC.DNSServerSearchOrder
if($DNSIPs -contains $OldDNSIP){
$NewDNS = $DNSIPs | foreach {$_ -replace $OldDNSIP,$NewDNSIP[0]}
$null = $NIC.SetDNSServerSearchOrder($NewDNS)
}
else{
write-host " - Old DNS server IP not found... ignoring"
}
}
Catch{
write-host " - Something went wrong... logging to a CSV for review later" -ForegroundColor Red
$FailedArray += [PSCustomObject]#{
'Computer' = $Nic.pscomputername
'NIC_ID' = $nic.index
'NIC_Descrption' = $nic.description}
}
}
ElseIf($NIC.DHCPEnabled -eq $true){
write-host " - DHCP is enabled. Adding this IP, Hostname, Nic Index and DHCP Server to a CSV for reviewing."
#add pscomputer, nic id, refernece and dhcp server to DHCPNICArray
$DHCPArray += [PSCustomObject]#{
'Computer' = $Nic.pscomputername
'NIC_ID' = $nic.index
'NIC_Descrption' = $nic.description
'DHCPEnabled' =$nic.dhcpenabled
'DHCPServer' = $nic.dhcpserver}
}
}
}
$DHCPArray | export-csv c:\temp\dhcp.csv -NoTypeInformation
$FailedArray | export-csv c:\temp\failed.csv -NoTypeInformation
If the WMI errors are due to a connection fail, they will occur before any of the nics are processed. If you want to catch them, and then continue with the next computer, you have to move the try-catch up to the level of that loop. If you also want to catch nic-specific errors, you need a 2nd try-catch at that level.
Also, consider using -ErrorAction Stop parameter, or specify $ErrorActionPreference = 'Stop', to make sure all errors are terminating (that means, jump right to the catch block).
Here's an example, with comments for explanation:
$ErrorActionPreference = 'Stop'
foreach ($Computer in $ComputerName) {
# add a try-catch per computer,
# to catch WMI errors
# and then continue with the next computer afterwards
try {
# if errors occur here,
# execution will jump right to the catch block the bottom
$NICList = Get-WmiObject Win32_NetworkAdapterConfiguration -ComputerName $Computer -ErrorAction Stop | where {
$_.IPEnabled -eq "TRUE"
}
foreach ($NIC in $NICList) {
# add a try-catch per NIC,
# to catch errors with this specific NIC and then
# continue with the next nic
try {
if ($NIC.DHCPEnabled -eq $false){
$DNSIPs = $NIC.DNSServerSearchOrder
if ($DNSIPs -contains $OldDNSIP) {
$NewDNS = $DNSIPs | foreach {$_ -replace $OldDNSIP,$NewDNSIP[0]}
$null = $NIC.SetDNSServerSearchOrder($NewDNS)
}
else {
write-host " - Old DNS server IP not found... ignoring"
}
}
elseif ($NIC.DHCPEnabled -eq $true){
write-host " - DHCP is enabled. Adding this IP, Hostname, Nic Index and DHCP Server to a CSV for reviewing."
$DHCPArray += [PSCustomObject]#{
'Computer' = $Nic.pscomputername
'NIC_ID' = $nic.index
'NIC_Descrption' = $nic.description
'DHCPEnabled' =$nic.dhcpenabled
'DHCPServer' = $nic.dhcpserver
}
}
}
catch {
write-host " - Configuring a NIC went wrong... logging to a CSV for review later" -ForegroundColor Red
# add nic-specific entry
$FailedArray += [PSCustomObject]#{
'Computer' = $Nic.pscomputername
'NIC_ID' = $nic.index
'NIC_Descrption' = $nic.description
}
}
# continue with next nic...
} # foreach nic
}
catch {
write-host " - Something else went wrong... logging to a CSV for review later" -ForegroundColor Red
# add entry for current computer
# (we don't know about nics, because wmi failed)
$FailedArray += [PSCustomObject]#{
'Computer' = $Computer
}
}
# continue with next computer...
} # foreach computer

How I can pass multiple array in value in PowerShell function

Below function I want to pass multiple value in array. When I'm passing more than one value I am getting an error.
function CheckProcess([String[]]$sEnterComputerNameHere, [String[]]$sEnterProccessNameHere) {
#Write-Host " $sEnterComputerNameHere hello"
#($sEnterComputerNameHere) | ForEach-Object {
# Calling Aarray
#($sEnterProccessNameHere) | ForEach-Object {
if (Get-Process -ComputerName $sEnterComputerNameHere | where {$_.ProcessName -eq $sEnterProccessNameHere}) {
Write-Output "$_ is running"
} else {
Write-Output "$_ is not running"
}
}
}
}
$script:sEnterProccessNameHere = #("VPNUI") # Pass the process agreement here
$script:sEnterComputerNameHere = #("hostname") # Pass the process agreement here
CheckProcess $sEnterComputerNameHere $sEnterProccessNameHere
Give it a try with this one:
Function CheckProcess([String[]]$sEnterComputerNameHere,[String[]]$sEnterProccessNameHere)
{ #Write-host " $sEnterComputerNameHere"
#($sEnterComputerNameHere) | Foreach-Object {
$computer = $_
Write-Host $computer
#($sEnterProccessNameHere) | Foreach-Object {
$process = $_
Write-Host $process
try{
$x = get-process -computername $computer #Save all processes in a variable
If ($x.ProcessName -contains $process) #use contains instead of equals
{
Write-Output "$process is running"
}
else
{
Write-Output "$process is not running"
}
}
catch
{
Write-Host "Computer $computer not found" -ForegroundColor Yellow
}
}
}
}
$script:sEnterProccessNameHere = #("VPNUI","Notepad++","SMSS")
$script:sEnterComputerNameHere = #("remotecomputer1","remotecomputer2")
CheckProcess -sEnterComputerNameHere $sEnterComputerNameHere -sEnterProccessNameHere $sEnterProccessNameHere
In general, it would be great if you write the error you get in your question. That helps others to help you.
If I work with arrays and | Foreach, I always write the $_in a new variable. That helps if I have another | Foreach (like you had) to know for sure, with which object I'm working with..
EDIT: I changed the script, so it uses "-contains" instead of "-eq" and I added a try/catch block, so if the other computer is not found, it gives you a message.. It works on my network
EDIT2: Do you have access to the other computers? If you run get-process -computername "name of remote computer" do you get the processes?

How do I return the value of a variable from within a Catch statement?

I need to test for three possible states of a remote computer: Online, No RPC Server, or No Response. I use a try-catch block to catch a non-terminating condition when the remote is online but the RPC server is unavailable. I understand why the following code doesn't return the status for No RPC Server but I do not know how I should proceed. Any assistance is greatly appreciated
$Status = ""
$hostname = Read-host("Enter Computer Name")
if (test-connection $hostname -Count 1 -ErrorAction SilentlyContinue){
$Status = "Online"
Try {
$x = gwmi -Class win32_ComputerSystem -ComputerName $hostname -ErrorAction Stop
}
Catch{
$Status = "No RPC"
Continue
}
}
Else{
$Status = "No Response"
}
$Status
If you don't need to do any other processing in the function there's no real reason to use the $Status variable at all. Just do a Return "No RPC" inside your Catch block(I'd also do the same inside your Else for consistency but that's entirely up to your preference).

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.

Powershell NSlookup on DC's Forward and Reverse

$topDC1="10.254.90.17"
$topDC2="10.225.224.17"
$topDC3="10.110.33.32"
$topDC4="10.88.100.10"
$DomainName="office.adroot.company.net"
TRY{
$hostname = [System.Net.DNS]::GetHostByName($topDC1).HostName.toupper()
$ipaddress = [System.Net.Dns]::GetHostAddresses($DomainName) | select IPAddressToString -ExpandProperty IPAddressToString
# I want the below to loop foreach ip in the object, ns it against all 4 topDC's, then output each result :(
$NS1 = nslookup $ipaddress[0] $topDC1
Write-host $NS1
}
Catch{
write-host "error"
}
Here is my dirty code so far (just to keep it simple)
I am trying to automate this:
NSLOOKUP office.adroot.company.net
put the results into an object
for each ip in results, do an NSLOOKUP against our top level DC's.
find which DC's haven't been cleaned up after decommission (still in dns)
$DCList="10.254.90.17","10.225.224.17","10.110.33.32","10.88.100.10"
$DomainName="office.adroot.blorg.net","pcd.blorg.ca","blorg.ca","percom.adroot.blorg.net", "blorg.blorg.net","ibg.blorg.net","sacmcm.adroot.blorg.net","sysdev.adroot.blorg.net","adroot.blorg.net"
TRY{
foreach ($DomainNameItem in $DomainName){
Write-Host ""
Write-Host ""
Write-Host "Looking UP result"$DomainNameItem -foreground yellow
Write-Host ""
$hostname = [System.Net.DNS]::GetHostByName($DCListItem).HostName.toupper()
$ipaddress = [System.Net.Dns]::GetHostAddresses($DomainNameItem).IPAddressToString
foreach ($ip in $ipaddress){
Write-Host ""
Write-Host "Looking UP result"$ip -foreground green
foreach ($topdns in $DCList){
$RESULTS = nslookup $ip $topdns
Write-host $RESULTS
}
}
}
}
Catch{
write-host "error"
}
Write-Host ""
Write-Host ""
pause
Got it! This will save me tonnes of work determining if a DNS cleanup is necessary. Thanks guys, I'm learning just how great Powershell can be :)
Try this:
$topDomainControllers = #("10.254.90.17", "10.225.224.17", "10.110.33.32", "10.88.100.10")
$DomainName="office.adroot.company.net"
try {
$hostname = [System.Net.Dns]::GetHostByName($topDC1).HostName.ToUpper()
$ipAddresses = [System.Net.Dns]::GetHostAddresses($DomainName) |
select -ExpandProperty IPAddressToString
foreach ($ipAddress in $ipAddresses) {
$nslookupResult = nslookup $ipAddress
$foundIp = $nslookupResult[1] -match "^\D*(\d+\.\d+\.\d+\.\d+)$"
if ($foundIp -eq $false) {
continue
}
$domainController = $Matches[1]
if ($topDomainControllers.Contains($domainController)) {
Write-Output -Verbose "Found domain controller match for $domainController"
break
} else {
Write-Output -Verbose "No match found for domain controller $domainController"
}
}
} catch {
Write-Output "An error has occured: $_"
}