Powershell try/catch with test-connection - powershell

I'm trying to have offline computers recorded in a text file so that I can run them again at a later time. Doesn't seem that it is being recorded or caught in catch.
function Get-ComputerNameChange {
[CmdletBinding()]
Param(
[Parameter(Mandatory=$True,ValueFromPipeline=$True,ValueFromPipelinebyPropertyName=$True)]
[string[]]$computername,
[string]$logfile = 'C:\PowerShell\offline.txt'
)
PROCESS {
Foreach($computer in $computername) {
$continue = $true
try { Test-Connection -computername $computer -Quiet -Count 1 -ErrorAction stop
} catch [System.Net.NetworkInformation.PingException]
{
$continue = $false
$computer | Out-File $logfile
}
}
if($continue){
Get-EventLog -LogName System -ComputerName $computer | Where-Object {$_.EventID -eq 6011} |
select machinename, Time, EventID, Message }}}

try is for catching exceptions. You're using the -Quiet switch so Test-Connection returns $true or $false, and doesn't throw an exception when the connection fails.
As an alternative you can do:
if (Test-Connection -computername $computer -Quiet -Count 1) {
# succeeded do stuff
} else {
# failed, log or whatever
}

The Try/Catch block is the better way to go, especially if you plan to use a script in production. The OP's code works, we just need to remove the -Quiet parameter from Test-Connection and trap the error specified. I tested on Win10 in PowerShell 5.1 and it works well.
try {
Write-Verbose "Testing that $computer is online"
Test-Connection -ComputerName $computer -Count 1 -ErrorAction Stop | Out-Null
# any other code steps follow
catch [System.Net.NetworkInformation.PingException] {
Write-Warning "The computer $(($computer).ToUpper()) could not be contacted"
} # try/catch computer online?
I've struggled through these situations in the past. If you want to be sure you catch the right error when you need to process for it, inspect the error information that will be held in the $error variable. The last error is $error[0], start by piping it to Get-Member and drill in with dot notation from there.
Don Jones and Jeffery Hicks have a great set of books available that cover everything from the basics to advanced topics like DSC. Reading through these books has given me new direction in my function development efforts.

Related

ForEach Loop failing to continue on the file?

The ForEach loop on this powershell script is failing to run more than one item before dropping out?
Can someone help me on this one?
function Get-RemoteLogonStatus {
[CmdletBinding()]
param(
[string]$ComputerName = ' '
)
ForEach ($line in Get-Content C:\ADComputers.csv)
{
$Computername = $line
if ( Test-Connection -ComputerName $ComputerName -Count 3 -Quiet ) {
try {
Get-WmiObject –ComputerName $ComputerName –Class Win32_ComputerSystem | Select-Object UserName = $lname -ErrorAction Stop | Out-Null
}
catch {
Write-Output 'No user logged in - RESTARTING.'
Shutdown /r /t 0 /M \\$ComputerName
$ComputerName
return
}
Write-Output 'Computer in use.'
$ComputerName
}
else {
Write-Output 'Computer in Use or is Offline.'
$ComputerName
}
}
$error.clear
}
Get-RemoteLogonStatus
Should run more than one item from the file. The file has 4 items for test:
a function is supposed to contain a block of code that can be repeated a number of times. Your function does all in one go, hence I don't see the need for it. Also it has the possibility to take one argument, but you don't pass it.
'return' is not necessary in PowerShell, it will throw the content of a variable without the need for a 'return'.
Select-Object needs a name that is being passed from the pipe, and not an assignment.
inside the try statement you might want to get an output, but if you pipe the line to Out-null you get nothing. and the catch will never grab any error.
the write-output are not clearly positioned, and difficult to understand.
I can infer what you are trying to achieve is: reboot computers in the csv file IF no user is logged in, is that so? In that case it's much simpler:
foreach ($ComputerName in (Get-Content C:\ADComputers.csv)) {
$User = Get-WmiObject -Class Win32_ComputerSystem -ComputerName $ComputerName | Select-Object UserName
if ($User.UserName -eq $null) {
Restart-Computer -ComputerName $ComputerName -Force
}
}
of if you want to stick to a function and see its purpose see this:
function Restart-Node {
param(
[Parameter(Mandatory=$true)][string]$ComputerName
)
$User = Get-WmiObject -Class Win32_ComputerSystem -ComputerName $ComputerName | Select-Object UserName
if ($User.UserName -eq $null) {
Restart-Computer -ComputerName $ComputerName -Force
}
}
foreach ($ComputerName in (Get-Content C:\ADComputers.csv)) {
Restart-Node -ComputerName $ComputerName
}

Output switching when re-arranging IF statement in Powershell

I am trying to write a script that tests the connection of computers in an OU and then tells me if they are on or off. This is going to be included in a script that has the end function of testing the connection, and if the computer is turned on, then performing a regedit but if it is turned off just skipping the computer and moving to the next one. However I am having trouble with the first bit of my If statement.
ForEach ($computer in $ADComputers)
{$TestConnection = Test-Connection -ComputerName $computer -Quiet
If ($TestConnection -eq 'True')
{Write-Host "$computer is on"}
Else
{Write-Host "$computer is off"}}
This code block returns the following results, which are correct
MS04D4C492C172 is on
MS04D4C492BADB is on
MS04D4C4AAE3F2 is on
MS04D4C492BAE2 is off
However doing it in the reverse will be easier for my syntax with the rest of the script that I wish to create, but if I try the following
ForEach ($computer in $ADComputers)
{$TestConnection = Test-Connection -ComputerName $computer -Quiet
If ($TestConnection -eq 'False')
{Write-Host "$computer is off"}
Else
{Write-Host "$computer is on"}}
My results are switched and I get this
MS04D4C492C172 is off
MS04D4C492BADB is off
MS04D4C4AAE3F2 is off
MS04D4C492BAE2 is on
I have tried running test connections manually on two of the computers and I get the following results
PS H:\> Test-Connection -ComputerName ms04d4c492c172 -Quiet
True
PS H:\> Test-Connection -ComputerName MS04D4C492BAE2 -Quiet
False
I don't understand why my output is switching. All I am doing is changing it from an if true to an if false statement? or am I doing something really silly here?
You are comparing to the string 'False'. Test-Connection -Quiet gives you a boolean value. Powershell's booleans are $true and $false. Only when printed out they are displayed as True and False. But still, $false and 'False' are different things.
If you use better variable names ($isOnline instead of $testConnection), and coherent indentation:
foreach ($computer in $ADComputers) {
$isOnline = Test-Connection -ComputerName $computer -Quiet
if ($isOnline) {
Write-Host "$computer is on"
} else {
Write-Host "$computer is off"
}
}
and then compare against an actual boolean value...
foreach ($computer in $ADComputers) {
$isOnline = Test-Connection -ComputerName $computer -Quiet
if ($isOnline -eq $false) {
Write-Host "$computer is off"
} else {
Write-Host "$computer is on"
}
}
BUT: Things like if ($booleanValue -eq $true) or if ($booleanValue -eq $false) are bad style.
The boolean value already is true or false. It's completely useless to compare it again. Therefore it's better written as if ($booleanValue) or if (-not $booleanValue), and it also is a lot more readable that way:
foreach ($computer in $ADComputers) {
$isOnline = Test-Connection -ComputerName $computer -Quiet
if (-not $isOnline) {
Write-Host "$computer is off"
} else {
Write-Host "$computer is on"
}
}

Run Parallel jobs power shell

Hello PowerShell gurus
I made this script to get the latest KB patch installed on a remote PC. And its now working. AL though its crude and something I just put together (while learning PS). It gets the job done… Painfully slow but works. It takes over 2:40 hrs to run and check 128 PCs. I saw in other posts that creating parallels jobs for this might help. I have no idea on how to create parallel jobs please help me.
my script
Start-Transcript -Path "$(Get-location)\RESULTLOG-$(Get-date -Format "yyyyMMddTHHmmss").log"
Function Get-FileName{
[System.Reflection.Assembly]::LoadWithPartialName(“System.windows.forms”) | Out-Null
$OpenFileDialog = New-Object System.Windows.Forms.OpenFileDialog
$OpenFileDialog.initialDirectory = Get-Location
$OpenFileDialog.filter = “All files (*.*)| *.*”
$OpenFileDialog.ShowDialog() | Out-Null
$OpenFileDialog.filename
}
$Importfile = Get-FileName
$Clients = Get-Content $Importfile
Foreach($Client in $Clients){
#CHECK IF PC IS ENABLED IN AD <<<<feature improvement
# if(get-adcomputer -Filter 'Name -like $p' -Properties Enabled)
if (Test-Connection -ComputerName $Client -Count 2 -Quiet) {
try {
Get-HotFix -ComputerName $Client -Description 'Security Update' | Select-Object -Last 1
}
catch {
"$Client;An error occurred."
}
}
else{
"$Client;not on line "
}
}
Stop-Transcript
I'm also trying to work on checking if PC is enabled in Active Directory before running the rest of the code, but I have that commented out for the mean time what I got works.
Can the performance in this be improved?? I know that network speed might have something to do but for the amount of data from each PC should not take this long
Take a look at the "-Parallel" switch of ForEach-Object:
https://devblogs.microsoft.com/powershell/powershell-foreach-object-parallel-feature/
You may need to change your code a bit in order to make it to work with the ForEach-Object call instead of the foreach block:
...
$Clients | ForEach-Object {
if (Test-Connection -ComputerName $_ -Count 2 -Quiet) {
try {
Get-HotFix -ComputerName $_ -Description 'Security Update' |
Select-Object -Last 1
}
catch {
"$_;An error occurred."
}
}
else {
"$_;not on line "
}
} -Parallel
...
EDIT: As mentioned by #doug-maurer in the comments as well as in the link I provided, this will only work on PowerShell v7-Preview-3 onwards.

PowerShell Script to Uninstall multiple programs from a list of servers

I have written a basic PS script in order to uninstall any programs defined in a text file ($appname) on all servers defined in a text file ($servers).
Running this command manually without the variables it works fine, however running the script via Jenkins or from PS command line it just hangs so I can't even debug, anyone have any ideas?
[array]$servers= Get-Content "D:\Jenkins\BuildUtilities\Citrix\CitrixServerList.txt"
[array]$appname= Get-Content "D:\Jenkins\BuildUtilities\Citrix\ProgramsList.txt"
ForEach($server in $servers) {
$prod=gwmi -ComputerName $server Win32_product | ?{$_.name -eq $appname}
$prod.uninstall()
}
To clarify: By running manually I mean running the following:
gwmi -ComputerName CTX-12 Win32_product | ?{_.Name -eq "Microsoft Word"}
Microsoft Word is an example.
It should have been easy to follow Matts hints
[array]$servers= Get-Content "D:\Jenkins\BuildUtilities\Citrix\CitrixServerList.txt"
[array]$appname= Get-Content "D:\Jenkins\BuildUtilities\Citrix\ProgramsList.txt"
ForEach($server in $servers) {
$prod=gwmi -ComputerName $server Win32_product | ?{ $appname -contains $_.name}
$prod.uninstall()
}
You are doing an comparison of two arrays, what you want to do is check for every instance of matching applications in the prod array you fetch from the server, so using the pipeline how you have it is not ideal.
[array]$servers= Get-Content "D:\Jenkins\BuildUtilities\Citrix\CitrixServerList.txt"
[array]$appname= Get-Content "D:\Jenkins\BuildUtilities\Citrix\ProgramsList.txt"
$DebugPreference= 'Continue'
foreach($server in $servers)
{
Write-Debug "Getting all installed applications on server $server"
$prod= Invoke-Command -ComputerName $server -Scriptblock {gwmi Win32_product}
Write-Debug "Installed applications collected, there are $($prod.Count) items in the array."
foreach($p in $prod)
{
Write-Debug "Searching apps array for the name $($p.Name)."
if($appname -contains $p.Name)
{
Write-Verbose -Message "$($p.Name) found on server $server, uninstalling."
$p.uninstall()
}
else
{
Write-Verbose -Message "$($p.Name) was not found on server $server."
}
}
}
EDIT: Since you brought up the speed issue, using Win32_product is very slow. So the faster method would be to get a list of installed applications from the registry key HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall (details how to do that are here). Build a new array of applications found that need uninstalled and then call on Win32_product. Just know that this only speeds up one portion of your script, and if any application is found, the slow down will still occur. The major speed gains will come from when you find a server that doesn't have any applications to uninstall.
EDIT2: Did some experimenting, using a simple Invoke-Command greatly speeds up the Get-WMIObject command. I did it on Services, using the command alone took 9 seconds, using the Invoke-Command took 1 second. Test:
$firstStart = Get-Date
$service1 = Get-WmiObject win32_service -ComputerName $ServerName
$firstEnd = Get-Date
$firstTime = New-TimeSpan -Start $firstStart -End $firstEnd
Write-Host "The first collection completed in $($firstTime.TotalSeconds) seconds." -ForegroundColor Green
$secondStart = Get-Date
$service2 = Invoke-Command -ComputerName $ServerName -ScriptBlock {Get-WmiObject win32_service}
$secondEnd = Get-Date
$secondTime = New-TimeSpan -Start $secondStart -End $secondEnd
Write-Host "The second collection completed in $($secondTime.TotalSeconds) seconds." -ForegroundColor Green

Error Handling Issue with Try/Catch in Powershell

I have been working on the following example from one of Don Jones' powershell books as part of my personal development and am having some serious trouble getting the try/catch construct to work as it should. As you can see, when the catch block executes, it sets a variable called $everything_ok to $false - which should trigger the else block in the following code. Which it does - the logfile is appended as per my expectations.
However it does not stop the script from ALSO executing the code in the if block and spewing out 'The RPC Server is unavailable' errors when it tries to query the made-up machine 'NOTONLINE' (Exception type is System.Runtime.InteropServices.COMException).
What makes this even stranger is that I went through the script with breakpoints, checking the contents of the $everything_ok variable along the way, and it never contained the wrong value at any point. So why on earth is the if block still executing for 'NOTONLINE' when the condition I have specified ( if ($everything_ok = $true) ) has not been met?
Am I doing something wrong here?
function get-systeminfo {
<#
.SYNOPSIS
Retrieves Key Information on 1-10 Computers
#>
[cmdletbinding()]
param (
[parameter(mandatory=$true,valuefrompipeline=$true,valuefrompipelinebypropertyname=$true,helpmessage="computer name or ip address")]
[validatecount(1,10)]
[validatenotnullorempty()]
[alias('hostname')]
[string[]]$computername,
[string]$errorlog = "C:\retry.txt",
[switch]$logerrors
)
BEGIN {
write-verbose "Error log will be $errorlog"
}
PROCESS {
foreach ($computer in $computername) {
try {$everything_ok = $true
gwmi win32_operatingsystem -computername $computer -ea stop
} catch {
$everything_ok = $false
write-verbose "$computer not Contactable"
}
if ($everything_ok = $true) {
write-verbose "Querying $computer"
$os = gwmi win32_operatingsystem -computername $computer
$cs = gwmi win32_computersystem -computername $computer
$bios = gwmi win32_bios -computername $computer
$props = #{'ComputerName' = $cs.__SERVER;
'OSVersion' = $os.version;
'SPVersion' = $os.servicepackmajorversion;
'BiosSerial' = $bios.serialnumber;
'Manufacturer' = $cs.manufacturer;
'Model' = $cs.model}
write-verbose "WMI Queries Complete"
$obj = new-object -type psobject -property $props
write-output $obj
}
elseif ($everything_ok = $false) {
if ($logerrors) {
"$computer $_" | out-file $errorlog -append
}
}
}
}
END {}
}
get-systeminfo -host localhost, NOTONLINE -verbose -logerrors
The equals sign in Powershell is used as the assignment operation. -eq is used to test for equality. So your if statement is assigning $true to $everything_ok, which then tests true.