Foreach and Test-Connection; ignoring unreachable machines

Hello,

I am working on a script that I want to do the following:

  1. Grabs a list of machines from a .txt file
  2. Does a Test-Connection on each item
  3. Ignores any machines that are turned off
  4. Create a new PSSession and send a scriptblock to all machines that are reachable

I am having issues with items 3 and 4. Here is my code so far.

$cred = Get-Credential domain\cred
$TargetSession = Get-Content C:\computers.txt

$Session = New-PSSession $TargetSession -Credential $cred

 foreach
    ($Computer in $session)
{
      if (Test-Connection -ComputerName $Computer.ComputerName -Quiet)
        {
        
            Invoke-Command -Session $Computer -ScriptBlock{
           Code here }
     
     else {Out-Null}    

I still get the error message:

Connecting to remote server OFFLINECOMPUTER failed with the following error message : WinRM cannot complete the operation. Verify that the specified computer name is valid, that the computer is accessible over the network, and that a firewall exception for the WinRM service is enabled and allows access from this computer.

I know I am missing something, but I cannot seem to find what it is. Any help would be greatly appreciated.

Ed

the issue you are having is you are trying to connect to a PSsession with a computer that is offline. you need to move the PSsession inside your check for connectivity.
Try This
also add ping count and buffer size so your script does not take all day. if it is a huge list of computers you might want to look into jobs

$cred = Get-Credential domain\cred
$TargetSession = Get-Content C:\computers.txt

 foreach
    ($Computer in $TargetSession)
{
      if (Test-Connection -ComputerName $Computer.ComputerName -BufferSize 16 -Count 1 -Quiet)
        {
            $Session = New-PSSession $Computer.ComputerName -Credential $cred
            Invoke-Command -Session $Session -ScriptBlock{
           Code here }
            Remove-PSSession -Session $Session 
         }
         else {Out-Null}     
}

Mark,

Thanks a bunch, that did the trick. I must have been looking at my code for too long and didn’t realize my mistake. Now the script works perfectly and I can finish up the rest of my tool this morning.

This will only be running against ~10 machines so the impact isn’t huge. But if I need to expand this in the future, I will look into jobs.

Cheers,
Ed

Happy I could help :smiley:

$pcnames = Import-Csv .\pcnames.csv
foreach ($name in $pcnames)
{
if (Test-Connection -ComputerName $name.ComputerName -BufferSize 16 -Count 1 -Quiet)
{
$pcnames = New-PSSession $name.ComputerName
Invoke-Command -Session $pcnames -ScriptBlock
{Set-LenovoBIOSSettings -ComputerName $name.ColumnName -SettingsToBeApplied $Settings}

        Remove-PSSession -Session $pcnames 
     }
     else {Out-Null}

Hey guys!

I have been struggling with a very similar issue. If you guys have any input on what I am doing wrong it would be greatly appreciated!

Just a stab at it, but it looks like you were missing a closing {,

See if this works

$pcnames = Import-Csv .\pcnames.csv
foreach ($name in $pcnames)
{
if (Test-Connection -ComputerName $name.ComputerName -BufferSize 16 -Count 1 -Quiet)

{
$pcnames = New-PSSession $name.ComputerName
Invoke-Command -Session $pcnames -ScriptBlock
{Set-LenovoBIOSSettings -ComputerName $name.ColumnName -SettingsToBeApplied $Settings}

Remove-PSSession -Session $pcnames
}

else {Out-Null}
}

I am still struggling

What error are you getting?

Function Set-LenovoBIOSSettings {
[CmdletBinding()]
param(
[Parameter(Mandatory=$false, ValueFromPipeline=$true,Position=0)]
[string]$ComputerName = (Get-Content env:computername),
[Parameter(Mandatory=$false, ValueFromPipeline=$true,Position=1)]
$SettingsToBeApplied)

Function Create-LogFile{
[CmdletBinding()]
    param([Parameter(Mandatory=$true, ValueFromPipeline=$true,Position=0)]
    [string]$LogName,
    [Parameter(Mandatory=$false, ValueFromPipeline=$true,Position=1)]
    [string]$LogfileVarName = 'LogFile',
    [Parameter(Mandatory=$false, ValueFromPipeline=$true,Position=2)]
    [string]$LogFileScope = 'Global')

    $LogFileString = "c:\windows\logs\$($LogName)_$(get-date -UFormat %m-%d-%y).log"
    if (Test-Path $LogFileString){Remove-Item -Path $LogFileString -Force}
    Set-Variable -Name $LogfileVarName -Value $LogFileString -Scope $LogFileScope -Force}

Function Write-Log {
[CmdletBinding()]
    param(
    [Parameter(Mandatory=$false, ValueFromPipeline=$true,Position=0)]
    [string]$message,
    [Parameter(Mandatory=$false, ValueFromPipeline=$true,Position=1)]
    [string]$LogName = "REPLACE",
    [Parameter(Mandatory=$false, ValueFromPipeline=$true,Position=2)]
    [string]$LogfileVarName = 'LogFile',
    [Parameter(Mandatory=$false, ValueFromPipeline=$true,Position=3)]
    [string]$LogFileScope = 'Global')

$MessageScriptBlock = {
if ($message.Length -eq 0){
    Write-Host " "
    " "| Out-File -FilePath $LogFile -Force -Append; Return}
if ($message -ne $null) {
    $message = "--"+ $message + " - $(Get-Date -Format "hh:mm:ss tt")--"
    Write-Host $message
    $message | Out-File -FilePath $LogFile -Force -Append}}

if ((Get-Variable $LogfileVarName) -eq $null) {
    if ((Get-Command -Name Create-LogFile) -ne $null) {Create-LogFile -LogName $LogName}
    else {
        Write-Host "There is no log file nor is there a `"Create-LogFile`" Function available"
        Write-Host "Creating a Logfile to continue the script: c:\windows\logs\REPLACE.log"
        $LogFileString = "c:\windows\logs\REPLACE.log"
        if (Test-Path $LogFileString){Remove-Item -Path $LogFileString -Force}
            Set-Variable -Name $LogfileVarName -Value $LogFileString -Scope $LogFileScope -Force}}

    Invoke-Command -ScriptBlock $MessageScriptBlock}

Create-LogFile -LogName SetLenovoBiosSettings

Write-Log “Updating the Bios Settings”
Write-Log “Start of Script. Date: $(get-date -UFormat %m-%d-%y)”
Write-Log “The original location of this log file is: $LogFile and this file was run on this computer: $ComputerName”

Write-Log “Here is the current running version of PowerShell:”
$PSVersionTable
#$PSVersionTable | Out-File -FilePath $LogFile -Append

if ($PSVersionTable.PSVersion.Major -lt 4) {Write-Log “Warning, this script has only been tested on PowerShell 4.”}
Write-Log
Write-Log
Write-Log “Here is the input parameter: `$SettingsToBeApplied: `”$SettingsToBeApplied`""

if (!(Test-Connection $ComputerName -Quiet)) {Write-Log “The computer $ComputerName is not pingable, make sure that it is on.”;Break}

if ((Get-WmiObject win32_computersystem -ComputerName $ComputerName).Manufacturer -notmatch “LENOVO”) {Write-Log “This computer is not a Lenovo, Breaking now”;Break}

Set-Variable -Name CurrentBIOSSettings -Value (Get-WmiObject lenovo_BIOSsetting -Namespace ‘Root\wmi’-ComputerName $ComputerName) -Scope Global

if ((Get-Variable -Name BIOSSettings) -eq $null) {Set-Variable -Name BIOSSettings -Value (Get-WmiObject lenovo_setBIOSsetting -namespace root\wmi -ComputerName $ComputerName) -Scope Global}

Write-Log “There is a total of $($SettingsToBeApplied.Count) setting(s) to be configured:”
$index = 1
Write-Log
Write-Log

$SetBIOSSettingScriptBlock = {
param([string]$SettingToBeApplied)

#Grabbing the Setting Name from the string which contains both the value and the name to make querying simpler, as we do not know what the value currently is.
$SettingName = $SettingToBeApplied.split(",")[0]
Write-Log
Write-Log
Write-Log
Write-Log
Write-Log "$index. Configuring $SettingName"
Set-Variable -Name index -Value ($index += 1) -Scope 1


$CurrentBIOSSetting = $CurrentBIOSSettings | where {($_.currentsetting).split(",")[0] -eq $SettingName} | Select-Object -Property CurrentSetting


Write-Log "Checking the `$SettingName variable, which is: `"$SettingName`", to make sure that the setting exists in the BIOS."
if ($CurrentBIOSSetting -eq $null) {Write-Log "The BIOS does not contain $SettingName as one of it's configurable BIOS Settings.";Return}


Write-Log "Here is the currently running configuration for this setting:"
$CurrentBIOSSetting | Out-File -FilePath $LogFile -Append -Force

 
if ($CurrentBIOSSetting.CurrentSetting -ne $SettingToBeApplied) {
    Write-Log "Trying to apply the Setting: $SettingToBeApplied now."
    $SuccessCode = $BIOSSettings.SetBIOSSetting($SettingToBeApplied).return
    
   
    if ($SuccessCode -eq "Invalid Parameter") {Write-Log "The setting: `"$SettingToBeApplied`" is not properly configured for this Machine\BIOS version."}
    
    
    elseif ($SuccessCode -eq "Success") {
        Write-Log "Successfully applied the setting, below is the newly configured value for this particluar setting:"
        
        #Grabing the latest instance of the BIOS setting object to log it's new value for confirmation\troubleshooting.
        Get-WmiObject lenovo_BIOSsetting -Namespace 'Root\wmi' -ComputerName $ComputerName | where {$($_.CurrentSetting.split(',')[0]) -eq $SettingName} | Select-Object -Property CurrentSetting | Out-File -FilePath $LogFile -Append}}


if ($CurrentBIOSSetting.CurrentSetting -eq $SettingToBeApplied) {
        Write-Log "The BIOS is already configured correctly. Below is the current values for both variables:"
        Write-Log "`"`$CurrentBIOSSetting.currentSetting`" is:"
        Write-Log
        $CurrentBIOSSetting.CurrentSetting | Out-File -FilePath $LogFile -Append -Force
        Write-Log
        Write-Log "`"`$SettingToBeApplied`" is:"
        Write-Log
        $SettingToBeApplied | Out-File -FilePath $LogFile -Append -Force}
        ((Get-WmiObject lenovo_savebiossettings -ComputerName $ComputerName -Namespace root\wmi).savebiossettings()).return}

if (($SettingsToBeApplied -isnot [string]) -and ($SettingsToBeApplied -isnot [array])) {$SettingsToBeApplied = $SettingsToBeApplied.tostring()}
if (($SettingsToBeApplied -is [string]) -and ($SettingsToBeApplied -match “,”)) {Invoke-Command -ScriptBlock $SetBIOSSettingScriptBlock -ArgumentList $SettingsToBeApplied}
if ($SettingsToBeApplied -is [array]) {

foreach ($SettingToBeApplied in $SettingsToBeApplied) {
    #Below is just some more error checking, making sure the input is correct
    if (($SettingToBeApplied -isnot [string]) -or (($SettingToBeApplied -is [string]) -and ($SettingsToBeApplied -notmatch ","))) {
    Write-Log "Each object defined in the array  as the SettingsToBeApplied paramter must be a string containing at least one comma to differentiate a setting name from it's corresponding value."
    Write-Log "The object: $SettingToBeApplied which is found at index#$($SettingsToBeApplied.IndexOf($SettingToBeApplied)) of the `"$SettingsToBeApplied`" fails meet the criteria. "}
    #Applying the BIOS Settings below.
    elseif (($SettingToBeApplied -is [string]) -and ($SettingToBeApplied -match ",")) {Invoke-Command -ScriptBlock $SetBIOSSettingScriptBlock -ArgumentList $SettingToBeApplied}}}}

$Settings = ‘Primary Boot Sequence,Hard Drive:Network Card:USB Key;[Exclude from boot order:USB Hard Disk:USB CD/DVD:Floppy Drive: USB Floppy:CD/DVD Drive]’

$pcnames = Import-Csv .\pcnames.csv
foreach ($name in $pcnames) {Set-LenovoBIOSSettings -ComputerName $name.name -SettingsToBeApplied $Settings}

basically I am trying to figure out a way to run this script continuously by using test connection after for each… I have tried numerous things and don’t even know where to start…

Before I try to answer what I think you’re asking about, I’d like to suggest some code organization to make it a bit easier to parse through. Not just for people here, but for yourself as well. Often the “not knowing where to start” feeling can come from writing too big of a script and having to keep it all in your head to try to figure out where it’s going wrong.

Make sure you’re indenting everything, which might have been lost in your copy/paste-ing. Also it’s helpful to put your loops and ifs on multiple lines, so this:

if (some comparisons with a bunch of options){do some stuff with a line wrap maybe}

would be more readable as this:

If (
    ('one') -and
    ('two') -and
    ('three')
   ){
    'do some stuff'
    'and some more stuff'
   }

also it looks like you have some functions nested inside your main function. Consider moving those out of the main function and just have them as separate items. It should work the same and will make your main function quite a bit smaller to the eyes.

Anyway all that aside what I’m guessing you’re running into is when you run this command:

foreach ($name in $pcnames) {Set-LenovoBIOSSettings -ComputerName $name.name -SettingsToBeApplied $Settings}

it’s stopping entirely after you hit your first offline machine. If that’s the case, it might have to do with your use of the ‘break’ command in your function. Try replacing that with ‘return’. It will still end that function immediately, but won’t end your entire script. As an example of this try this sample code:

$list = @('1','2','3')
Function test{
    param($item)
    "Iteration number $item"
    If ($item -eq '1'){break}
    "end of iteration $item"
}
foreach ($item in $list){test $item}

This should call the function once for each item in my list, similar to looping through your csv import. Notice that the output dies on the first run, before the “end of iteration” for that loops. It stops completely because of the break.

Now replace {break} with {return} and check the output. You don’t see “end of iteration 1” because the functions ends immediately after hitting the {return} command, but it goes on to the next item and loops through with 2 and 3. I’m guessing that’s more like what you’re after.

Jeremy

I have altered the code to this and receive a return function “success” however the bios changes do not seem to save.

$pcnames = Import-Csv pcnames.csv
foreach ($name in $pcnames){
(gwmi -class Lenovo_SetBiosSetting –namespace root\wmi).SetBiosSetting(“WakeOnLAN,Disable”)
}