A few scripts to print and edit relationships between devices - particularly useful with the new HS4 views (and hopefully more useful once they sort out the relationships mess on grid view). Output goes to the log.

Lists all relationships on one line
Code:
Imports Scheduler.Classes

Sub Main(parms as object)
  Dim enumerator as clsDeviceEnumeration = hs.GetDeviceEnumerator

  Dim dev as DeviceClass
  Dim dev2 as DeviceClass
  Dim output as String

  Do
    dev = enumerator.GetNext

    If dev Is Nothing Then Continue Do

    Log(dev.Ref(hs) & " " & dev.Name(hs) & " " & dev.Relationship(hs))

    Select Case CInt(dev.Relationship(hs))
      ' INDETERMINATE
      Case 1
        output = output & ",I" & dev.Ref(hs)

      ' PARENT
      Case 2
        output = output & ",P" & dev.Ref(hs)

        Dim assoc = dev.AssociatedDevices(hs)

        For i as Integer = 0 to assoc.Length - 1
          dev2 = GetDevice(assoc(i))

          If CInt(dev2.Relationship(hs)) <> 4 Then
            Log("ERROR: Parent " & dev.Ref(hs) & " has non-child (" & dev2.Relationship(hs) & ") association " & dev2.Ref(hs))
            Exit Sub
          End If

          output = output & ",C" & dev2.Ref(hs)
        Next

      ' STANDALONE
      Case 3
        output = output & ",S" & dev.Ref(hs)

      ' CHILD
      Case 4
        ' child, do nothing

      ' UNKNOWN
      Case Else
        output = output & ",U" & dev.Ref(hs)

    End Select

  Loop Until enumerator.Finished

  Log(output)
End Sub

Sub Log(str)
  hs.WriteLog("Relationships", str)
End Sub

Function GetDevice(ref) as DeviceClass
  Dim dev as DeviceClass = hs.GetDeviceByRef(ref)

  If dev Is Nothing Then
    Throw New Exception("Device " & ref & " not found")
  End If

  Return dev
End Function
Print relationships in (moderately) readable format. Note newline isn't shown, so copy the entry and use an editor to replace | with newline.
Code:
Imports Scheduler.Classes

Sub Main(parms as object)
  Dim enumerator as clsDeviceEnumeration = hs.GetDeviceEnumerator

  Dim dev as DeviceClass
  Dim dev2 as DeviceClass
  Dim output as String

  Do
    dev = enumerator.GetNext

    If dev Is Nothing Then Continue Do

    Log(dev.Ref(hs) & " " & dev.Name(hs) & " " & dev.Relationship(hs))

    Select Case CInt(dev.Relationship(hs))
      ' INDETERMINATE
      Case 1
        output = output & "I " & dev.Ref(hs) & " " & dev.Name(hs) & " {" & dev.AssociatedDevices_List(hs) & "}|"

      ' PARENT
      Case 2
        output = output & "P " & dev.Ref(hs) & " " & dev.Name(hs) & " {" & dev.AssociatedDevices_List(hs) & "}|"

        Dim assoc = dev.AssociatedDevices(hs)

        For i as Integer = 0 to assoc.Length - 1
          dev2 = GetDevice(assoc(i))

          If CInt(dev2.Relationship(hs)) <> 4 Then
            Log("ERROR: Parent " & dev.Ref(hs) & " has non-child (" & dev2.Relationship(hs) & ") association " & dev2.Ref(hs))
            Exit Sub
          End If

          output = output & "--- C" & dev2.Ref(hs) & " " & dev2.Name(hs) & " {" & dev2.AssociatedDevices_List(hs) & "}|"
        Next

      ' STANDALONE
      Case 3
        output = output & "S " & dev.Ref(hs) & " " & dev.Name(hs) & " {" & dev.AssociatedDevices_List(hs) & "}|"

      ' CHILD
      Case 4
        ' child, do nothing

      ' UNKNOWN
      Case Else
        output = output & "? " & dev.Ref(hs) & " " & dev.Name(hs) & " {" & dev.AssociatedDevices_List(hs) & "}|"

    End Select

  Loop Until enumerator.Finished

  Log(output)
End Sub

Sub Log(str)
  hs.WriteLog("Relationships", str)
End Sub

Function GetDevice(ref) as DeviceClass
  Dim dev as DeviceClass = hs.GetDeviceByRef(ref)

  If dev Is Nothing Then
    Throw New Exception("Device " & ref & " not found")
  End If

  Return dev
End Function
Set relationships based on the script parameter provided
Code:
Imports Scheduler.Classes

Sub Main(parms as object)
  Dim p() As String = CStr(parms).Split(",")

  Dim ref as Integer
  Dim dev as DeviceClass
  Dim parent as DeviceClass
  Dim action as String

  For n as Integer = 0 to p.Length - 1

    If p(n).Length < 2 Then
      Log("ERROR: Invalid command " & p(n))
      Exit Sub
    End If

    Log("Command " & p(n))

    action = p(n).Substring(0, 1)
    ref = CInt(p(n).Substring(1))

    dev = hs.GetDeviceByRef(ref)

    If dev Is Nothing Then
      Log("ERROR: Device " & ref & " not found")
      Exit Sub
    End If

    Select Case action

      ' STANDALONE
      Case "S"
        Log("Standalone " & ref & " " & dev.Name(hs))
        Disassociate(dev)
        dev.Relationship(hs) = 3 ' Standalone
        parent = Nothing


      ' PARENT
      Case "P"
        Log("Parent " & ref & " " & dev.Name(hs))
        If CInt(dev.Relationship(hs)) <> 2 Then ' not Parent
          Disassociate(dev)
        End If

        dev.Relationship(hs) = 2 ' Parent

        parent = dev

      ' CHILD
      Case "C"
        Log("Child " & ref & " " & dev.Name(hs))
        If parent is Nothing Then
          Log("ERROR: set child not valid without parent")
          Exit Sub
        End If

        If CInt(dev.Relationship(hs)) <> 4 Then ' not Child
          Disassociate(dev)

          parent.AssociatedDevice_Add(hs, ref)
          dev.AssociatedDevice_Add(hs, parent.Ref(hs))
          dev.Relationship(hs) = 4 ' Child

        ElseIf dev.AssociatedDevices_List(hs) <> CStr(parent.Ref(hs)) Then
          Disassociate(dev)

          parent.AssociatedDevice_Add(hs, ref)
          dev.AssociatedDevice_Add(hs, parent.Ref(hs))
          dev.Relationship(hs) = 4 ' Child
        End If

      ' UNKNOWN ACTION
      Case Else
        Log("ERROR: Unknown action " & action)
        Exit Sub

    End Select

  Next

  Log("Finished")  
End Sub

Sub Log(str)
  hs.WriteLog("Relationships", str)
End Sub

Function GetDevice(ref) as DeviceClass
  Dim dev as DeviceClass = hs.GetDeviceByRef(ref)

  If dev Is Nothing Then
    Throw New Exception("Device " & ref & " not found")
  End If

  Return dev
End Function

Sub Disassociate(dev as DeviceClass)
  Dim assoc() as Integer = dev.AssociatedDevices(hs)

  If assoc.Length = 0 Then
    Log("No associations")
    Exit Sub
  End If

  For i as Integer = 0 to assoc.Length - 1
    Dim dev2 as DeviceClass = GetDevice(assoc(i))

    Log("Disassociating " & dev.Ref(hs) & " from " & dev2.Ref(hs))

    dev2.AssociatedDevice_Remove(hs, dev.Ref(hs))
    dev.AssociatedDevice_Remove(hs, dev2.Ref(hs))
  Next
End Sub
For list and set, a specific format for the parameter / output is used:

(action / type)(device reference),etc,etc

action: S = standalone, P = parent, C = child (also U = unknown and I = indeterminate which don't make much sense)
device reference = numeric device ref

e.g. P10,C15,C26,S5

Indicates / sets device 10 as the parent of devices 15 and 26, and device 5 as standalone

Child or standalone devices will be unlinked from any parent if necessary. Child must immediately follow parent, and existing child devices of the parent are not affected.

Bigger example: P101,C102,C104,C105,C106,C107,C108,C109,C111,P110,C103,S112, P113,C114,P116,C115,S117,P118,C119,C121,C122

An advantage of this approach is that you can swap between configurations by running the script with different parameters, or event set up different events to run quickly.

If you want a graphical approach, I recommend Jon00's grouping tool.