Invoke-Command with Try/Catch and adding to an external array

How would I add a computer to an array within an Invoke-Command cmdlet also utilizing try/catch statement?

I have the following which isn’t behaving as I’m expecting:

"*** CONFIGURING IIS ON REMOTE COMPUTERS ***" #| Tee-Object -Append $LogFile
# Run config command on array - Results will come back unsorted due to the parallel processing nature of invoke-command
Invoke-Command -ComputerName $IisServers -ErrorAction stop {
Try {
# Import PowerShell module (Only essential for Win 2k8r2 servers)
Import-Module WebAdministration
# Remove filename from list if exists
If (Get-WebConfiguration -PSPath 'MACHINE/WEBROOT/APPHOST' -Filter 'system.webServer/defaultDocument/files/add' | Where-Object {$_.value -eq $Using:FileName}) {
Remove-webconfigurationproperty /system.webServer/defaultDocument -name files -atElement @{value=$Using:Filename}
Write-Output "$Env:ComputerName`: '$Using:FileName' removed from Default Document list"
}
Else {
Write-Output "$Env:ComputerName`: '$Using:FileName' doesn't exist in Default Document list"
}
}
Catch {
$ErrorMessage = $_.Exception.Message
Write-Output "$Env:ComputerName`: $ErrorMessage"
$ExecutionIssues += $Env:ComputerName
}
$props = @{ComputerName=$ExecutionIssues}
New-Object -Type PSObject -Prop $Props
} #| Tee-Object -Append $LogFile
  • The If/Else loop is being ignored and each time I run it, it just runs the If section regardless.
  • The Try/Catch appears to be working for execution of the If command errors but the "ErrorAction Stop" argument appears to be terminating the script on the first WinRM connection error it hits rather than logging and continuing with the rest.
  • Nothing is being added to the $ExecutionIssues variable on failure.
Am I missing something obvious?

Please fix the code in the post. You should be using PSObjects to return the information you need. There was not an -ErrorAction Stop on the Remove action. In this scenario, you are going to get back the computername and one of three statuses:

try {
    Import-Module WebAdministration 
    
    If (Get-WebConfiguration -PSPath 'MACHINE/WEBROOT/APPHOST' -Filter 'system.webServer/defaultDocument/files/add' | Where-Object {$_.value -eq $Using:FileName}) { 
        Remove-webconfigurationproperty -Filter '/system.webServer/defaultDocument' -name files -atElement @{value=$Using:Filename} -ErrorAction Stop
        
        [pscustomobject]@{
            ComputerName = $env:COMPUTERNAME
            Status       = 'Success'
        }      
    }
    else {
        [pscustomobject]@{
            ComputerName = $env:COMPUTERNAME
            Status       = 'Not found: File not in configuration'
        }
    } 
}
catch {
    [pscustomobject]@{
        ComputerName = $env:COMPUTERNAME
        Status       = 'Error: {0}' -f $_
    }
}

Edit: Also noticed that your Get and Remove are using different paths. The GET ‘system.webServer/defaultDocument/files/add’ and in the remove you are not using ‘add’ anywhere. Has this been tested on a local IIS server before you run it on other servers?

I’m struggling to read your post. Make sure you are doing code blocks for each code section. Repost and maybe I can follow it.

I’ve tried to update the formatting a number of times but every time I do the thread disappears for most the day and then when I can access it again, the formatting is exactly the same again. I’ll try again but this is painful…

Thanks for the suggestion Rob, looking good. I have found another solution as it goes which simply stores all the errors in the $errmsg variable and outputs them to log at the end. This does actually work well but have no control over the formatting so your suggestion looks much better thanks. I’ll let you know how I get on.

Hi Rob,

Yes, the Get and Remove are meant to be different as the command to check if a file exists is not the same command used for removing/adding files. These commands have been tested and work as expected.

Just looking at your code and think if I try and simplify my target it may help as still can’t see how I can get the computername added to a variable which can be used externally from the the Invoke-Command script block.

$TestArray = @()
$Win2k8r2Computers="Server1","Server2"
Invoke-Command -ComputerName $Win2k8r2Computers {
   Try {
       If (Get-Service | Where-Object {$_.Name -eq "W3SVC"} -ErrorAction Stop ) {
           Write-Output "IIS is installed on $Env:ComputerName (Win2008r2)"
           }
       Else {
           <add the computername to the array $TestArray>
           }
   }
   catch {
       [pscustomobject]@{
          ComputerName = $env:COMPUTERNAME
          Status = 'Error: {0}' -f $_
       }
   }
}

I just need the computer to be added to the array for manipulation later. I know you mention that I need to return the item using pscustomobject but can’t see how this would aid in adding to the array?

The site definitely has issues… BUT if you read the formatting guide and then take your time to format it correctly you’ll have less issues. If your post disappears, please just put a message to the mods in the tech support section, they will release it fairly quickly.

It’s not that I’m unsure how to format, it’s that the code PowerShell formatting option is simply missing from the RTF bar above for me sometimes.

If the button is missing, you can either switch to Text view and use the CRAYON button or refresh the page a few times. Usually after two or three refreshes the <> comes back for me.

Ok, I’m now trying to return information from the Invoke-Command using custom objects. I tried using Rob’s method but servers running old versions of PowerShell weren’t outputting the message as expected.

The below seems to give me better results, however, still have a few issues:

$Win2k8r2Computers="Server1","Server2","Server3"

$results = Invoke-Command -ComputerName $Win2k8r2Computers { #}
$props = @{}
Try {
If ($PSVersionTable.PSVersion.Major -eq "2") {
$props.Add('Message',"Server (Win2008r2) is currently running an incompatible version of PowerShell (v2.1)")
}

ElseIf (Get-Service | Where-Object {$_.Name -eq "W3SVC"} -ErrorAction Stop ) {
$props.Add('Message',"IIS is installed (Win2008r2)")
}
Else {
$props.Add('Message',"IIS is NOT installed (Win2008r2)")
}
}
catch {
$props.Add('Message','Error: {0}' -f $_)
}
New-Object -Type PSObject -Prop $Props
} | Sort-Object PSComputerName | Select-Object PSComputerName,Message | FT -AutoSize

$results
  • The catch section doesn't appear to be returning anything in the $results variable
  • How do I go about assigning the computers, depending on result, to variables now I have the information outside of the Invoke-Command block? I'm thinking of using an If/Else statement based on keywords in the Message value string but not sure how to check against this as $results.message doesn't appear to work.

I would remove your pipe after closing out the Invoke-Command script block. With this in place you are assigning a formatted table to $results instead of the actual objects you want.

$Win2k8r2Computers="Server1","Server2","Server3"
 
$results = Invoke-Command -ComputerName $Win2k8r2Computers { 
    $props = @{}
    Try {
        If ($PSVersionTable.PSVersion.Major -eq "2") {
            $props.Add('Message',"Server (Win2008r2) is currently running an incompatible version of PowerShell (v2.1)")
        } #if
 
    ElseIf (Get-Service | Where-Object {$_.Name -eq "W3SVC"} -ErrorAction Stop ) {
        $props.Add('Message',"IIS is installed (Win2008r2)")
        } #elseif
    Else {
        $props.Add('Message',"IIS is NOT installed (Win2008r2)")
        } #else
    } #try
    catch {
        $props.Add('Message','Error: {0}' -f $_)
    } #catch
    New-Object -Type PSObject -Prop $Props
} #Invoke-Command Script Block

#you can now do what you want with $results, if statements etc.
foreach ($result in $results) {
   if ($result.message -eq "IIS is installed (Win2008r2)") {
      #do stuff
   } #if
}#foreach

#to view your formatted table do this: 
$results | 
    Sort-Object PSComputerName | 
        Select-Object PSComputerName,Message | 
            FT -AutoSize

 

Ah yes that makes sense thanks. I can now call my data using $results.message which helps a great deal. Any ideas on why my catch doesn’t appear to be catching though?

[pscustomobject] type accelerator was available in version 3. If you are running v2, you should seriously consider updating those servers to 5.1 for security reasons alone. As Mike R assisted, you can use a New-Object -TypeName PSObject -Property $myHashTable to support older versions.

What are your indications that it is not working? Are you getting an error message or is the script terminating?

What is the purpose of the try/catch. It appears the only possible terminating error in your try block would be the Get-Service… Are you just trying to determine if that service is installed? If so, you don’t need a try/catch block to do that.

$Win2k8r2Computers="Server1","Server2","Server3"
 
$results = Invoke-Command -ComputerName $Win2k8r2Computers { 
    $props = @{}
    If ($PSVersionTable.PSVersion.Major -gt 2) {
        If (Get-Service | Where-Object {$_.Name -eq "W3SVC"}) {
            $props.Add('Message',"IIS is installed (Win2008r2)")
        } #if IIS
        Else {
            $props.Add('Message',"IIS is NOT installed (Win2008r2)")
        } #else no IIS
    } #if version -gt 2
    Else {   
        $props.Add('Message',"Server (Win2008r2) is currently running an incompatible version of PowerShell (v2.1)")
    } #else old PS

    New-Object -Type PSObject -Prop $Props
} #Invoke-Command Script Block

#you can now do what you want with $results, if statements etc.
foreach ($result in $results) {
    if ($result.message -eq "IIS is installed (Win2008r2)") {
        #do stuff
    } #if
}#foreach

#to view your formatted table do this: 
$results | 
    Sort-Object PSComputerName | 
        Select-Object PSComputerName,Message | 
            FT -AutoSize

 

 

That’s the problem, I’m not in the position to be able to update PowerShell on these servers so need to filter them out in order to just work on the ones I can.

I can’t see any previous reference to New-Object -TypeName PSObject -Property $myHashTable in any of the replies above. Maybe I’m missing something?

Regardless, I’m good to go other than the Catch simply not catching now and just can’t see why it’s not working.

The accelerator can’t be used, so you need to do it like this:

try {
    Import-Module WebAdministration 
    
    If (Get-WebConfiguration -PSPath 'MACHINE/WEBROOT/APPHOST' -Filter 'system.webServer/defaultDocument/files/add' | Where-Object {$_.value -eq $Using:FileName}) { 
        Remove-webconfigurationproperty -Filter '/system.webServer/defaultDocument' -name files -atElement @{value=$Using:Filename} -ErrorAction Stop
        
        New-Object -TypeName PSObject -Property @{
            ComputerName = $env:COMPUTERNAME
            Status       = 'Success'
        }      
    }
    else {
        New-Object -TypeName PSObject -Property @{
            ComputerName = $env:COMPUTERNAME
            Status       = 'Not found: File not in configuration'
        }
    } 
}
catch {
    New-Object -TypeName PSObject -Property @{
        ComputerName = $env:COMPUTERNAME
        Status       = 'Error: {0}' -f $_
    }
}

BTW, I really have not paid a lot attention to what you are trying to do, just getting your code working, but now that I look at it, is seems you are just trying to get the PS major version and determine if a service is running on remote servers. If that’s all it is, I would avoid the whole creating a message to return from the remote machine and just collect the data. You can analyze it however you want from the local machine after. Like this:

$Win2k8r2Computers="Server1","Server2","Server3"
 
$results = Invoke-Command -ComputerName $Win2k8r2Computers { 
    $props = @{}
    $props.Add("services", (Get-Service))
    $props.Add("PSVersion", $PSVersionTable)
    New-Object -Type psobject -Property $props
}

# $results now contains at least 3 properties PSComputerName, Services (which have all the installed services,
# and PSVersion which has the remote machines $PSVersionTable hashtable
# you can access them with the property derefernce operator . i.e. $results[0].PSversion.PSVersion.Major

$results |
    Sort-Object PSComputerName
        Select-Object PSComputerName,
                      @{n="IIS";e={[bool]($_.services | Where-Object name -eq "W3SVC")}},
                      @{n="PSVers";e={$_.PSVersion.PSVersion.Major}} |
            Format-Table -AutoSize

 

Thanks Rob. I’m actually working on a different part of the script now but the subject is still relevant. Here’s my actual code with your suggestion so it’s clear what I’m trying to achieve:

$Win2k8r2Computers="Server1","Server2","Server3","Server4"

$results = Invoke-Command -ComputerName $Win2k8r2Computers { #}
    Try {
        If ($PSVersionTable.PSVersion.Major -eq "2") {
                New-Object -Type PSObject -Prop @{
                    Message = "Server (Win2008r2) is currently running an incompatible version of PowerShell (v2.1)"
                }
            }
        ElseIf (Get-Service | Where-Object {$_.Name -eq "W3SVC"} -ErrorAction Stop) {
                New-Object -Type PSObject -Prop @{
                    Message = "IIS is installed (Win2008r2)"
                }
            }
        Else {
            New-Object -Type PSObject -Prop @{
                Message = "IIS is NOT installed (Win2008r2)"
            }
        }
    }
    catch {
        New-Object -Type PSObject -Prop @{
            Message = 'Error: {0}' -f $_
        }
    }
}
$results

This simply shows errors in the console but doesn’t catch them in a controlled way still. I’ve purposely left out the ComputerName property for all the statements as will simply use PSComputerName.

[quote quote=278073]BTW, I really have not paid a lot attention to what you are trying to do, just getting your code working, but now that I look at it, is seems you are just trying to get the PS major version and determine if a service is running on remote servers. If that’s all it is, I would avoid the whole creating a message to return from the remote machine and just collect the data. You can analyze it however you want from the local machine after. Like this:
<textarea class=“urvanov-syntax-highlighter-plain print-no” style=“tab-size: 4; font-size: 14px !important; line-height: 18px !important; z-index: 0; opacity: 0;” readonly=“readonly” data-settings=“dblclick”></textarea>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
$Win2k8r2Computers="Server1","Server2","Server3"
$results = Invoke-Command -ComputerName $Win2k8r2Computers {
$props = @{}
$props.Add("services", (Get-Service))
$props.Add("PSVersion", $PSVersionTable)
New-Object -Type psobject -Property $props
}
# $results now contains at least 3 properties PSComputerName, Services (which have all the installed services,
# and PSVersion which has the remote machines $PSVersionTable hashtable
# you can access them with the property derefernce operator . i.e. $results[0].PSversion.PSVersion.Major
$results |
Sort-Object PSComputerName
Select-Object PSComputerName,
@{n="IIS";e={[bool]($_.services | Where-Object name -eq "W3SVC")}},
@{n="PSVers";e={$_.PSVersion.PSVersion.Major}} |
Format-Table -AutoSize
[/quote]

Thanks Mike but how does this handle connection errors though. I’m trying to assign failed connections to a variable so they can be output to the bottom of a log file to flag as needing manual attention.

Also, I’ve tried your code and the output is not showing information as expected:

services : {System.ServiceProcess.ServiceController, System.ServiceProcess.ServiceController, System.ServiceProcess.ServiceController, System.ServiceProcess.ServiceController...} PSVersion : {PSRemotingProtocolVersion, BuildVersion, PSCompatibleVersions, PSVersion...} PSComputerName : ComputerName RunspaceId : fb32f0bb-da71-42e0-8155-507bdb1c5966
I'm not sure why the RunspaceId is present either as Select-Object should be excluding this.

What is the error you are receiving? If you only have the one property, there is no need for a custom object, just return the string. PSComputername will still be added as a property.

PSComputerName and RunspaceID properties are created automatically with Invoke-Command. If you want to catch connection issues, it would have to be handled by the Invoke-Command and not inside the remote hosts script block. Here is a simplified example. The variable $connectionissues will capture all the errors. You can call it later and even output the contents to a file if you want.

Invoke-Command -ComputerName localhost, member -ScriptBlock {Get-Process} -ErrorVariable connectionissues -ErrorAction SilentlyContinue

$connectionissues

$connectionissues will have a targetobject property which will be the computername with the connection issue.