This discussion has been locked. The information referenced herein may be inaccurate due to age, software updates, or external references.
You can no longer post new replies to this discussion. If you have a similar question you can start a new discussion in this forum.

How to use Powershell groups.ps1 script with foreach command?

Hi All,

Our team is in need of creating roughly 600-800 groups. I have used the group.ps1 script to create a 1 group at a time, but it would great if we could get some automagic working emoticons_happy.png

The objective is to have a group per building per priority . There would be potential to have 4 groups per building. I.E.

BldA P1     BldA P2     BldA P3     ...

BldB P1     BldB P2     BldB P3     ...

BldC P1     BldC P2     BldC P3     ...

...                ...               ...               ...

My issue is I have no clue how to add the 'foreach' command into existing script, or what variables I may need to establish.  I have an idea to create a csv, or txt file with the list of buildings, but past that I am unsure how to make the logic work. Here is what I am working with so far.  Ideally the fields in green would be a variable that is pulled from the txt file.

==================================================================================================================================================================

$members = @(

    @{ Name = "0284 - Kentucky Clinic P3"; Definition = "filter:/Orion.Nodes[CustomProperties.NetworkLayer!='UPS' AND CustomProperties.Building='0284 - Kentucky Clinic' AND CustomProperties.Priority='3']" }

)

$groupId = (Invoke-SwisVerb $swis "Orion.Container" "CreateContainer" @(

    # group name

    "0284 - Kentucky Clinic P3",

    # owner, must be 'Core'

    "Core",

    # refresh frequency

    60,

    # Status rollup mode:

    # 0 = Mixed status shows warning

    # 1 = Show worst status

    # 2 = Show best status

    0,

    # group description

    "Priority 3 Nodes in 0284 - Kentucky Clinic.",

    # polling enabled/disabled = true/false (in lowercase)

    "true",

    # group members

    ([xml]@(

       "<ArrayOfMemberDefinitionInfo xmlns='http://schemas.solarwinds.com/2008/Orion'>",

       [string]($members |% {

         "<MemberDefinitionInfo><Name>$($_.Name)</Name><Definition>$($_.Definition)</Definition></MemberDefinitionInfo>"

         }

       ),

       "</ArrayOfMemberDefinitionInfo>"

    )).DocumentElement

  )).InnerText

==================================================================================================================================================================

Suggestions/Solutions on where the foreach logic needs to be placed and formatted?

  • Here's the simple logic:

    Create a list of all of your buildings:

    $Buildings = "Building 1", "Building 2", ... "Building X"

    Create a list of your priorities:

    $Priorities = "P1", "P2".. "PX"

    ForEach ( $Building in $Buildings )
    {

      Write-Host "Working on $Building"

       ForEach ( $Priority in $Priorities )

      {

         Write-Host "Working on $Priority"

         # Here's where you do the stuff in the "groups.ps1" script (basically just copy and paste it in here and then change the variables to match.

      }

    }

  • I think I may have been over thinking that.  But it worked to perfection!  We have over 200 buildings so a changed it up a bit.  I created a txt file with our buildings had the foreach loop pull from that file.  Do you see any issue with this below?  It has worked for a small test at first

    $Buildings = Get-Content "C:\Users\test\Desktop\Buildings.txt"

    ForEach ( $Building in $Buildings )

    {

      Write-Host "Working on $Building"

    ......

  • Nope - from a PowerShell perspective, that's 100% fine.

  • I am just seeing this now but its really along the path of an idea I am looking to work out.

    So far I have a very similar pattern but my questions really come up when I am trying to work out the filters.

    For example "filter:/Orion.Nodes[IP_Address=$Subnets]"

    I have a loop written as followed

    $Locations = @("GA1001","GA1002","GA1003")
    $Subnets = @("10.10.10.1", "10.10.20.1", "10.10.30.1")
    
                  
    
    
    for ($i = 0; $i -lt $Locations.Length -and $i -lt $Subnets.Length ; $i++){
    
        Write-Host 
        
    
            $members = @(
               @{ Name = "$Locations - Firewall"; Definition = "filter:/Orion.Nodes[IP_Address=$Subnets]"}
            )
    
            
            $groupId = (Invoke-SwisVerb $swis "Orion.Container" "CreateContainer" @(
            # group name
            "$Locations - Firewall",
    
            # owner, must be 'Core'
            "Core",
    
            # refresh frequency
            120,
    
            # Status rollup mode:
            # 0 = Mixed status shows warning
            # 1 = Show worst status
            # 2 = Show best status
            0,
    
            # group description
            "MX",
    
            # polling enabled/disabled = true/false (in lowercase)
            "true",
    
            # group members
            ([xml]@(
                "<ArrayOfMemberDefinitionInfo xmlns='http://schemas.solarwinds.com/2008/Orion'>",
                [string]($members |% {
                "<MemberDefinitionInfo><Name>$($_.Name)</Name><Definition>$($_.Definition)</Definition></MemberDefinitionInfo>"
                }
                ),
            "</ArrayOfMemberDefinitionInfo>"
            )).DocumentElement
          )).InnerText
    }

    If you look at my filters, I really need to have those increment as well like anything else in a loop. It runs 3 times and makes the groups but those filters are wrong and I would have to go and correct each one which doesn't do much automating.

    The output should be

    GA1001 - Firewalls

    • Dynamic Query: IP_Address is 10.10.10.1

    GA1002 - Firewalls

    • Dynamic Query: IP_Address is 10.10.20.1

    GA1003 - Firewalls

    • Dynamic Query: IP_Address is 10.10.30.1

    instead I get all kinds of ridiculous text values. This works great if there is a single array but anything more, even a hash table, and I get problems. Is there a way to modify the way it accepts a dynamic query?

  • You are using a For loop and not a ForEach. Either is fine, but you'd need call out each element for each pass through the loop.  There's a lesson here that I learned the hard way on a production environment, so let me give you a heavily commented code sample.

    $Locations = @("GA1001","GA1002","GA1003")
    $Subnets = @("10.10.10.1", "10.10.20.1", "10.10.30.1")
    
    For ( $i = 0; $i -lt $Locations.Count; $i++ ) {
        # Just $Locations will return ALL of the locations (the entire array)
        # $Locations[$i] will return the 'i-th' element
        # When you put array elements within a string, this get wonky
        #    Write-Host "$Locations[$i] should have $Subnets[$i]"
        # Returns
        #    GA1001 GA1002 GA1003[0] should have 10.10.10.1 10.10.20.1 10.10.30.1[0]
        #    GA1001 GA1002 GA1003[1] should have 10.10.10.1 10.10.20.1 10.10.30.1[1]
        #    GA1001 GA1002 GA1003[2] should have 10.10.10.1 10.10.20.1 10.10.30.1[2]
        # That's because the script interpreter tries it's best to convert variables to strings
        # but it assumes (incorrectly in this place) that the bracket means the variable name is done
    
        # To get around that, you can encompass nearly *anything* inside of $( ____ )
        # and it will render the contents *inside* of the parenthesis, then send
        # it back to the string.
        Write-Host "$( $Locations[$i] ) should have $( $Subnets[$i] )"
    
        # This now returns:
        #    GA1001 should have 10.10.10.1
        #    GA1002 should have 10.10.20.1
        #    GA1003 should have 10.10.30.1
    }

  • Oh - and you don't specifically need the @( and ) wrappers around arrays - at least not in newer versions of PowerShell.

    Something else for you to consider about working with 'complex' data types in PowerShell.  Again an overly commented code sample...

    # Your original way
    # -- this is fine, but you need to be 100% sure that the n-th element matches between each
    $Locations = @( "GA1001","GA1002","GA1003" )
    $Subnets = @("10.10.10.1", "10.10.20.1", "10.10.30.1")
    # The Good: it's short and sweet
    # The bad: it's prone to errors if the 37th element in one array doesn't match the 37th element in the other
    
    # -- other options --
    # As an array of hashtables (not my favorite, but it works)
    # Build an empty array to hold my group details
    $GroupDefinitions = @()
    
    # Add a hashtable (the += operator says to add this thing to the current list)
    $GroupDefinitions += @{
        Location = "GA1001" ;
        Subnet   = "10.10.10.1"
    }
                  
    $GroupDefinitions += @{
        Location = "GA1002" ;
        Subnet   = "10.10.20.1"
    }
    
    $GroupDefinitions += @{
        Location = "GA1003" ;
        Subnet   = "10.10.30.1"
    }
    
    # You would access each elements by the placement in the array and then by the element name
    # Variable      Index   Name
    $GroupDefinitions[0]["Location"]
    $GroupDefinitions[0]["Subnet"]
    # The good: Each set of subnets/locations is 'grouped' together.  Makes errors less possible
    # The bad:  Combining integers and strings for getting to elements can get confusing
    #           It takes more characters to define it this way
    
    
    ####
    # My favorite is an array of PowerShell Objects
    ####
    # Build an empty array to hold the things.
    $PsGroupDefinitions = @()
    
    # Use a *very* similar syntax to build a PowerShell object
    # You can see that the value for the parameter property is the same as the above hashtable
    $PsGroupDefinitions += New-Object -TypeName PsObject -Property @{
        Location = "GA1001" ;
        Subnet   = "10.10.10.1"
    }
    
    $PsGroupDefinitions += New-Object -TypeName PsObject -Property @{
        Location = "GA1002" ;
        Subnet   = "10.10.20.1"
    }
    
    $PsGroupDefinitions += New-Object -TypeName PsObject -Property @{
        Location = "GA1003" ;
        Subnet   = "10.10.30.1"
    }
    
    # To access the 'Location' of the first element (index 0)
    # Variable      Index   Name
    $PsGroupDefinitions[0].Location
    
    # To access the 'Subnet' of the third element (index 2)
    # Variable      Index   Name
    $PsGroupDefinitions[2].Subnet
    
    # The good: Keeps elements paired together much like the hashtable example
    #           Uses standard 'dot' accessing, like most PowerShell objects
    #           Because this is a PowerShell object, you can 'extend it' with Add-Member
    #           Super-easy to use this with a ForEach loop
    <#
    ForEach ( $Group in $PsGroupDefinitions ) {
        Write-Host "The current location is: $( $Group.Location )"
        Write-Host "The current subnet is:   $( $Group.Subnet )"
    }
    #>
    
    # The bad:  it's longer (more characters) to build the things

  • KMSigma,

    Thank you, your correct syntax has helped and its now showing the correct answers. Also thank you for bringing up hashtables. I've got a few prototypes built out and I did touch upon these examples but since I was struggling with the syntax for strings within the dynamic queries I sort of stopped. I will post what is working for me - it definitely does not change too much upon the original code but if I can develop something with less errors I will always prefer that route.

    Here' s what is working - I am going to play around using my example code in a test environment using hashtables

     #FIREWALL
    
    $Locations = ("GA1001","GA1002","GA1003")
    $Subnets = @("10.10.10.1", "10.10.20.1", "10.10.30.1")
    
     for( $i = 0; $i -lt $Locations.Count; $i++ ) {
        
            $members = @(
               @{ Name = "$($Locations[$i] ) - Firewall"; Definition = "filter:/Orion.Nodes[IP_Address=$($Subnets[$i] )]" }
            )
         
            
            $groupId = (Invoke-SwisVerb $swis "Orion.Container" "CreateContainer" @(
            # group name
            "$($Locations[$i] )",
    
            # owner, must be 'Core'
            "Core",
    
            # refresh frequency
            120,
    
            # Status rollup mode:
            # 0 = Mixed status shows warning
            # 1 = Show worst status
            # 2 = Show best status
            0,
    
            # group description
            "MX",
    
            # polling enabled/disabled = true/false (in lowercase)
            "true",
    
            # group members
            ([xml]@(
                "<ArrayOfMemberDefinitionInfo xmlns='http://schemas.solarwinds.com/2008/Orion'>",
                [string]($members |% {
                "<MemberDefinitionInfo><Name>$($_.Name)</Name><Definition>$($_.Definition)</Definition></MemberDefinitionInfo>"
                }
                ),
            "</ArrayOfMemberDefinitionInfo>"
            )).DocumentElement
          )).InnerText
        
    
    }

    One other question that you probably have the expirence to help with KMSigma.

    I've been running through the example code on Github but I haven't quite solved how to next a group into a parent group using these commands. Maybe much like the syntax that got me I am missing something here.

    # ADDING A SUBGROUP
    #
    # Adding up devices in the group.
    #  
    $subgroupmembers = @(
        @{ Name = "Subgroup Devices-Up"; Definition = "filter:/Orion.Nodes[Status=1]" },
        @{ Name = "Subgroup Devices-NotReachable"; Definition = "filter:/Orion.Nodes[Status=12]" }
    )
    
    $subgroupId = (Invoke-SwisVerb $swis "Orion.Container" "CreateContainer" @(
        # group name 
        "Sample PowerShell SubGroup",
    
        # owner, must be 'Core'
        "Core",
    
        # refresh frequency
        60,
    
        # Status rollup mode:
        # 0 = Mixed status shows warning
        # 1 = Show worst status
        # 2 = Show best status
        0,
    
        # group description
        "Sub Group created by the PowerShell sample script.",
    
        # polling enabled/disabled = true/false (in lowercase)
        "true",
    
        # group members
        ([xml]@(
           "<ArrayOfMemberDefinitionInfo xmlns='http://schemas.solarwinds.com/2008/Orion'>",
           [string]($subgroupmembers |% {
             "<MemberDefinitionInfo><Name>$($_.Name)</Name><Definition>$($_.Definition)</Definition></MemberDefinitionInfo>"
             }
           ),
           "</ArrayOfMemberDefinitionInfo>"
        )).DocumentElement
      )).InnerText 
    
    # Add the SubGroup to a group
    
    $subgroupUri = Get-SwisData $swis "SELECT Uri FROM Orion.Container WHERE ContainerID=@id" @{ id = $subgroupId }
    
    Invoke-SwisVerb $swis "Orion.Container" "AddDefinition" @(
    	# group ID
    	$groupId,
    
    	# group member to add
    	([xml]"
    		<MemberDefinitionInfo xmlns='http://schemas.solarwinds.com/2008/Orion'>
    		    <Name></Name>
    		    <Definition>$subgroupUri</Definition>
    	    </MemberDefinitionInfo>"
    	).DocumentElement
    ) | Out-Null 

    line 5-41 we are making the group with members but line 48 - 51 we are adding it to a group? I am not sure where it explicitly states its adding it to a parent group or how we define that PARENT GROUP. Most of my groups are already created but need these subgroups under each. It can become a real nested mess if we introduce another array targeting each PARENT GROUP in the same way we run through each object - sort of how I have it above.

    To give you an idea my environment looks much like this

    PARENT GROUP

    • SWITCHES
      • MEMEBERS
    • FIREWALLS
      • MEMBERS
    • SERVERS
      • MEMBERS
    • ACCESS POINTS
      • MEMBERS

    If I wanted to create 5 groups and tell them to all nest themselves under PARENT GROUP, how would I go about achieving this?

    Thank you for all your time and help, you already got me past a huge hurdle

  • For most things I haven't done before (or not done in a very long time), I normally do one in the Web Console and then see what it looks like in SWQL Studio.

    SELECT TOP 1000 DefinitionID, ContainerID, Name, Entity, FromClause, Expression, Definition FROM Orion.ContainerMemberDefinitionSELECT TOP 1000 DefinitionID, ContainerID, Name, Entity, FromClause, Expression, Definition
    FROM Orion.ContainerMemberDefinition
    WHERE Name = 'Thing I just built'

    If you only have a handful to build, then I'd just build them by hand.  The 'nesting' syntax can get difficult with all the various Container ID's floating around.


    <random thought incoming>

    I was thinking about this and I I honestly do not know if the API will allow a definition like:

    filter:/Orion.Groups[StartsWith(Name,'GA1')]

    You'd have to make doubly sure that the 'parent' group doesn't start with GA1 in this case otherwise it would be a child of itself and that doesn't work in Back to the Future or anywhere else.

    I think the syntax is valid, but the system may baulk at letting you define a dynamic group filter inside the group. (You can't do it within the Web Console at all)  I can see that this could cause a recursive nightmare and understand why this would be blocked.  Like I said, I haven't tried it myself.

    </random thought>

  • Understood - for now I've just taken the liberty of moving each group into their subgroups. Still much faster than creating groups 1 at a time.

    One problem I am seeing using the member definition is this error on the Orion webpage when just attempting to look at the Dynamic Query

    Unexpected Website Error

    Object reference not set to an instance of an object.

    Thing is the query works just fine - group is named correctly and the right objects are in the container yet I cannot look or edit the Dynamic Query - I am not sure if this is going to be a problem later.

    My code is below

    $PsGroupDefinitions = @()
    
    
    $PsGroupDefinitions += New-Object -TypeName PsObject -Property @{
        Location = "GA1001 - Access Points" ;
    	Caption = "GA1001MR"
    }
    
    
    
    
    ForEach ($Group in $PsGroupDefinitions){
        
        Write-Host "Working on Group $($Group.Location) with Caption $($Group.Caption)"
    
    
        $members = @(
               @{ Name = "$($Group.Location)"; Definition = "filter:/Orion.Nodes[StartsWith(Caption,'$($Group.Caption)']" }
        )
    
    
    
        $groupId = (Invoke-SwisVerb $swis "Orion.Container" "CreateContainer" @(
            # group name
            "$($Group.Location)",
    
    
            # owner, must be 'Core'
            "Core",
    
            # refresh frequency
            120,
    
            # Status rollup mode:
            # 0 = Mixed status shows warning
            # 1 = Show worst status
            # 2 = Show best status
            0,
    
            # group description
            "MR",
    
            # polling enabled/disabled = true/false (in lowercase)
            "true",
    
            # group members
            ([xml]@(
                "<ArrayOfMemberDefinitionInfo xmlns='http://schemas.solarwinds.com/2008/Orion'>",
                    [string]($members |% {
                "<MemberDefinitionInfo><Name>$($_.Name)</Name><Definition>$($_.Definition)</Definition></MemberDefinitionInfo>"
                }
            ),
                "</ArrayOfMemberDefinitionInfo>"
                     )).DocumentElement
                        )).InnerText
    
    }

  • Yeah - the website blocks "Groups" from the drop downs in the selector for entity type (by design, see previous statement about recursion).

    You are hosed if you build a group with a group dynamic query and want to view it in the web console.  Like I said, I can see the why of it from an architecture perspective.