Foreach and Test-Connection; ignoring unreachable machines

This topic contains 11 replies, has 5 voices, and was last updated by  Jeremy Murrah 2 months ago.

  • Author
    Posts
  • #31826

    Ed Grant
    Participant

    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

  • #31827

    Mark Hammonds
    Participant

    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}     
    }
    
  • #31846

    Ed Grant
    Participant

    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

  • #31856

    Mark Hammonds
    Participant

    Happy I could help 😀

  • #81601

    Jason Frazee
    Participant

    $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}

  • #81604

    Jason Frazee
    Participant

    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!

    • #81608

      Alex Brown
      Participant

      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}
      }

  • #81620

    Jason Frazee
    Participant

    I am still struggling

    • #81623

      Alex Brown
      Participant

      What error are you getting?

  • #81628

    Jason Frazee
    Participant

    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}

  • #81629

    Jason Frazee
    Participant

    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...

  • #81631

    Jeremy Murrah
    Participant

    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.

You must be logged in to reply to this topic.