Output switching when re-arranging IF statement in Powershell - 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"
}
}

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
}

Using test-connection and IF = "True" Statement, ELSE not working when no connection

I am trying to test a network connection and if it's good, tell me it is found. ELSE write-host it is not found, and export that computer to a CSV. However, I am just getting the error "Testing connection to computer failed: error due to lack of resources." on computers that cannot get a connection. Does test-connection not work this way? The = "True" is working for me. Below is my code:
foreach ($computer in $Computers)
$computername = $computer.Computers
$testConnection = Test-Connection $computername
IF ($testConnection = "True")
{
{
Write-Host "$computername found"
}
ElSE
{
Write-Host "$computername does not have CCleaner installed." #| Export-Csv C:\Users\jcheng\Desktop\Scripts\PingTestLog.txt -Append
}
{
Give this a try
foreach ($computer in $Computers)
$computername = $computer.Computers
$testConnection = Test-Connection $computername
If (($testConnection -ne "") -or ($testconnection -ne $null){
Write-Host "$computername found"
}
Else{
Write-Host "$computername does not have CCleaner installed." #| Export-Csv C:\Users\jcheng\Desktop\Scripts\PingTestLog.txt -Append
}
I would check on this: $computer.Computers, to see if it is actually returning the property that you are looking for.
As a side note, make sure your CCleaner version is newer than 5.33 :) it was recently breached
Use this
If ((Test-Connection $computername).PingSucceeded)
{ ... }

PowerShell 'if' condition

Here is a quick little PowerShell blurp that is supposed to get the device's IP address, Service Tag, and MAC address of any active MAC addresses on the device (active meaning there is a connection).
Anyway, if I don't use an if statement, this works perfectly, but if I do, it only executes the first line:
Test-Connection $computername -count 1 | select #{Name="Computername";Expression={$_.Address}},Ipv4Address
If the device is on... and the last line of the device is off.
Write-Host "Device is offline"
Here is my little PowerShell 'script':
$computername = Read-Host 'Enter Computer Name'
$online = Test-Connection -Computername $computername -BufferSize 16 -Count 1 -Quiet
IF ($online -eq "True") {
Test-Connection $computername -count 1 | select #{Name="Computername";Expression={$_.Address}},Ipv4Address
Get-WmiObject win32_SystemEnclosure -computername $computername | select serialnumber
Get-wmiobject -class "Win32_NetworkAdapterConfiguration" -computername $computername |Where{$_.IpEnabled -Match "True"}
} Else {
Write-Host "Device is offline"
}
Why does this happen? What might I be doing wrong?
Boolean variables in PowerShell are $true and $false. If should be:
if ($online -eq $true) {
Or
if($online)
Try this:
$computername = Read-Host 'Enter Computer Name'
$online = Test-Connection -Computername $computername -BufferSize 16 -Count 1 -Quiet
IF ($online -eq $true) {
Write-Host "Device is online"
Test-Connection $computername -count 1 | select #{Name="Computername";Expression={$_.Address}},Ipv4Address
try {
[PSObject[]]$systemEnclosures = Get-WmiObject win32_SystemEnclosure -computername $computername -ErrorAction Stop
Write-Host "Found $($systemEnclosures.Count) System Enclosures"
$systemEnclosures | select serialnumber
[PSObject[]]$NetworkAdapterConfiguration = Get-wmiobject -class "Win32_NetworkAdapterConfiguration" -computername $computername -ErrorAction Stop
Write-Host "Found $($NetworkAdapterConfiguration.Count) Network Adapter Configurations"
$NetworkAdapterConfiguration = $NetworkAdapterConfiguration | Where{$_.IpEnabled}
Write-Host "Found $($NetworkAdapterConfiguration.Count) IP Enabled Network Adapter Configurations"
$NetworkAdapterConfiguration
} catch {
Write-Host "An Error Occurred"
Write-Host $_.ToString() #show exception info
}
} Else {
Write-Host "Device is offline"
}
NB: I'm not suggesting you keep this code in your final script; just use this to understand what's happening behind the scenes.
Per comments; use $true instead of "true", as though both are truthy, using the wrong type will lead to a false understanding of the language / some really odd bugs down the line where you find that lines like if($true -eq "false") {write-output "Well, this is unusual"} will cause some odd behaviour.
Also you may wish to look into replacing Write-Host with Write-Output for any logical return values, or Write-Verbose/Write-Debug for any informative/investigation outputs; then call the code with the appropriate switches / preferences... but that's unrelated to your issue. More on that is in Write-Host Considered Harmful.
Update
Per comments, the issue you're seeing is a bug gotcha: https://github.com/PowerShell/PowerShell/issues/4552
If the data coming out of this code is just to be displayed in the console, you can avoid this issue by explicitly calling the Format-Table command:
$computername = Read-Host 'Enter Computer Name'
IF (Test-Connection -Computername $computername -BufferSize 16 -Count 1 -Quiet) {
Test-Connection $computername -count 1 | select #{Name="Computername";Expression={$_.Address}}, 'Ipv4Address' | Format-Table
Get-WmiObject win32_SystemEnclosure -computername $computername | select serialnumber | Format-Table
Get-wmiobject -class "Win32_NetworkAdapterConfiguration" -computername $computername | Where{$_.IpEnabled} | Format-Table
} Else {
Write-Host "Device is offline"
}
If you need the output to go to the pipeline for consumption elsewhere, all's good as it is (i.e. without the format-table piece); the objects are being correctly written to the pipeline; the issue is simply that when it comes to displaying all of the results together, the first object causes PowerShell to create columns ComputerName and Ipv4Address, and PowerShell subsequently attempts to display those properties of the following objects, despite those not having such properties. That said, this could be improved by putting the different object types into different properties of a custom object for easy reference. For example,
$computername = Read-Host 'Enter Computer Name'
If (Test-Connection -Computername $computername -BufferSize 16 -Count 1 -Quiet) {
(new-object -TypeName PSObject -Property #{
ConnectionTest = Test-Connection $computername -count 1 | select #{Name="Computername";Expression={$_.Address}}, 'Ipv4Address'
SystemEnclosures = Get-WmiObject win32_SystemEnclosure -computername $computername | select serialnumber
NetworkAdapterConfigs = Get-wmiobject -class "Win32_NetworkAdapterConfiguration" -computername $computername | Where{$_.IpEnabled}
})
} Else {
Write-Host "Device is offline"
}
Test-Connection -Quiet returns a Boolean, not a string. Try if ($online) or if ($online -eq $true) if you want to be explicit.
https://learn.microsoft.com/en-us/powershell/module/Microsoft.PowerShell.Management/Test-Connection?view=powershell-5.1

Powershell try/catch with test-connection

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.

Powershell ForEach loop with embedded IF statements

Starting to write powershell scripts (very new) because SCCM tends to respond better to them (both client and server)
So with the above stated here is my first script:
#Changes the 'ProvisioningMode' Key in the registry to False
$ProvisiongMode = New-ItemProperty -Path Registry::HKLM\SOFTWARE\Microsoft\CCM\CcmExec -Name ProvisioningMode -Value False -Force
#Clears or 'nulls' the SystemTaskExcludes key in the registry
$SystemTaskExludes = New-ItemProperty -Path Registry::HKLM\SOFTRWARE\Microsoft\CCM\CcmExec -Name SystemTaskExcludes - Value "" - Force
#----------------------------------------------------------------------------------------------
$Success = "C:\Path\to.log"
$Failure = "C:\Path\to.log"
$Computers = Import-Csv "C:\Path\to.csv"
$SearchStr = Get-ItemProperty -Path Registry::HKLM\SOFTWARE\Microsoft\CCM\CcmExec | select-object ProvisioningMode
$Online = Test-Conntection -Computername $ComputerName -Count 1 -Quiet
ForEach ($ComputerName in $Computers)
if ($Online -eq 'False')
{
Write-Output $ComputerName`t'Connection Failed' >> $Failure
}
Else
{
if ($SearchStr -eq True)
{
$ProvisioningMode
$SystemTaskExcludes
}
}
#Second Check
if ($SearchStr -eq 'False')
{
Write-Output $ComputerName`t'Registry has been changed' >> $Success
}
The issue in question is the $Online variable. I would like to see if a computer is responsive to ping, if true then proceed to run $ProvisioningMode and $SystemTaskExclude.
Then the other issue is querying that key to see if it changed. The issue with that one is $SearchStr = Get-ItemProperty -Path Registry::HKLM\SOFTWARE\Microsoft\CCM\CcmExec | select-object ProvisioningMode returns
ProvisionMode
-----------------
False
And I cant grab just the false data.
Like I stated; very new at powershell and writing something that I will use helps me learn.
Edit: What I Have tried is
ForEach ($Name in $Computers)
{
Test-Connection -BufferSize 2 -Computername $Name.ComputerName -Count 1 -Quiet | Write-Output $Online
}
if ($Online -eq 'True') {Write-Output $Name`t'Computer is online' >> C:\Online.txt}
And many variations of the same thing.
Test-Connection -BufferSize 2 -Computername $Name.ComputerName -Count 1 -Quiet
Returns Data, which is what I want, but I need to input that into an If statement and still retain the $Name for the $StringStr and log files.
Those of you wondering, this takes the client out of provisioning mode when running an OSD. It fixes the 'No Self-Signed Certificate' issue.
Even though the string representations of boolean values in PowerShell are True and False, the correct way to compare againt such a value is with the $true and $false variables.
Furthermore, assign the result of Test-Connection to $Online with =:
$Online = Test-Connection -BufferSize 2 -Computername $Name.ComputerName -Count 1 -Quiet
if($Online -eq $true){
# Machine responds to ping, do stuff!
}
But the comparison is actually unnecessary. If $Online already equals $frue or $false, you can use it on its own inside the if statement:
if($Online){
# Machine responds to ping, do stuff!
}
I assume that $ProvisionMode, $SystemTaskExcludes and $SearchStr are all statements that you want to execute on the remote machine, not on the SCCM server itself.
To do so, you will need to connect to the machine and instruct it to execute the *-ItemProperty statements.
# Enclosing statements in {} creates a ScriptBlock - a piece of code that can be invoked later!
$ProvisionMode = {
#Changes the 'ProvisioningMode' Key in the registry to False
New-ItemProperty -Path Registry::HKLM\SOFTWARE\Microsoft\CCM\CcmExec -Name ProvisioningMode -Value False -Force
}
$SystemTaskExludes = {
#Clears or 'nulls' the SystemTaskExcludes key in the registry
New-ItemProperty -Path Registry::HKLM\SOFTRWARE\Microsoft\CCM\CcmExec -Name SystemTaskExcludes - Value "" - Force
}
$SearchStr = {
Get-ItemProperty -Path Registry::HKLM\SOFTWARE\Microsoft\CCM\CcmExec | Select-Object -ExpandProperty ProvisioningMode
}
#----------------------------------------------------------------------------------------------
$LogFilePath = "C:\Path\to.log"
$Computers = Import-Csv "C:\Path\to.csv"
foreach($Computer in $Computers){
$Online = Test-Connection -Computername $Computer.Name -Count 1 -Quiet
if(-not $Online)
{
"$ComputerName`t'Connection Failed'" | Out-File -FilePath $LogFilePath -Append
}
else
{
$SearchResult = Invoke-Command -ComputerName $Computer.Name -ScriptBlock $SearchStr
if ($SearchResult)
{
# The call operator (&) invokes the scriptblock
Invoke-Command -ComputerName $Computer.Name -ScriptBlock $ProvisionMode
Invoke-Command -ComputerName $Computer.Name -ScriptBlock $SystemTaskExludes
}
else # SearchStr must be $false, or non-existing
{
"$ComputerName`t'Registry has been changed'" | Out-File -FilePath $LogFilePath -Append
}
}
}
For simplicity, I've used Invoke-Command with the -ComputerName parameter, but in a real world situation, I would set up a PSSession with New-PSSession, and reuse that for the connection with Invoke-Command -Session