API Action Creation multiple targets using ComputerName

Greetings Programs…

I need help in understanding how to correctly add multiple targets to my action.xml template. At this point I’m not sure if I am having issues understanding the BES.xml, issues understanding how to properly modify XML with PowerShell, or maybe both. I’m reaching out here first in hopes someone with BigFix knowledge can at least point in me a useful direction.

I have a working powershell script that that utilizes the API to upload a file, create a task, and then take an action against that task. The script’s current single parameter is the package name to pull from a Nexus repository and I would now like to add a parameter to pass in targets.

I have been using the below code and XML snippet with hardcoded ComputerName and now need to make them dynamic based on the passed in values. Currently I use the below code to modify the XML and set <FixletID> to the value returned by the create task function.

I’ve tried a number of ways to make the same general type of change against <Target> and <ComputerName> but running into error after error.

` $FixletID = $TaskObject.BESAPI.Task.ID
  Write-Log "Creating action against Task ID $FixletID"
  $stubTemplate = "$PSScriptRoot\ActionTemplate.xml"
  $global:myXML = New-Object xml
  $myXML.Load($stubTemplate)
  $myXML.BES.SourcedFixletAction.SourceFixlet.FixletID = $FixletID
  $body = $global:myXML


    <?xml version="1.0" encoding="UTF-8"?>
    <BES xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="BES.xsd">
     <SourcedFixletAction>
       <SourceFixlet>
         <Sitename>Our Cool Site Name</Sitename>
         <FixletID>SOMENUMBER</FixletID>
         <Action>Action1</Action>
       </SourceFixlet>
       <Target>
         <ComputerName>computer001</ComputerName>
         <ComputerName>computer002</ComputerName>
         <ComputerName>computer003</ComputerName>
       </Target>
     </SourcedFixletAction>
    </BES> `

From the XML above are the “ComputerName” lines considered XML elements? When I ask PowerShell about the various nodes I get the following;

myXML System.Xml.XmlDocument
BES System.Xml.XmlElement
SourcedFixletAction System.Xml.XmlElement
Target System.Xml.XmlElement
ComputerName System.String`

I see the below in BES.xml but it isn’t helping me much.

<xs:complexType name="BESActionTarget"> <xs:choice> <xs:element name="ComputerName" type="xs:normalizedString" maxOccurs="unbounded" /> <xs:element name="ComputerID" type="xs:nonNegativeInteger" maxOccurs="unbounded" /> <xs:element name="CustomRelevance" type="xs:normalizedString" maxOccurs="1"/> <xs:element name="AllComputers" type="xs:boolean" maxOccurs="1" /> </xs:choice> </xs:complexType>

If I try and set the value of computername to another string …

$myXML.BES.sourcedFixletAction.target.computername = [string]"somestring"

it throws an error that “only strings can be used as values to set XmlNode properties.”

What type of XML construct are the ComputerName lines?

Yes, the ComputerName entries should be XML elements. Without seeing the PowerShell script you’re using, it’s difficult to guess where you may be getting stuck. Are you even using the XML functions like ‘createElement’ or ‘appendChild’ ?

I’m a lot more comfortable with Python than PowerShell myself, but it is completely possible to do this in PS. Here’s a link to get started https://devblogs.microsoft.com/powershell/adding-elements-to-xml-objects-with-windows-powershell/

I do via python.

def build_xml(computer_target,set):

  action_script="""\nbegin prefetch block\
\nadd nohash prefetch item  url="""+url_S+"""\
\nend prefetch block\
\ndelete /var/tmp/dynaXX.sh\
\ndelete /var/tmp/opt_dyna_fs.sh\
\ndelete /var/tmp/host_grp.csv\
\nextract bf_arch_dyna "/var/tmp"\
\nwait chmod 755 /var/tmp/opt_dyna_fs.sh\
\nwait /bin/sh /var/tmp/opt_dyna_fs.sh """+cmd_argument+"""\
\ndelete /var/tmp/host_grp.csv\
\ndelete /var/tmp/opt_dyna_fs.sh\
\ndelete /var/tmp/dynaXX.sh """

  #print(action_script)
  xml_query='<?xml version="1.0" encoding="utf-8"?>\
    <BES xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" SkipUI="true">\
    <SingleAction>\
        <Title>Deploy: DynaTrace '+change_no+' '+set+' '+cmd_argument+'</Title>\
          <Relevance>exists (operating system) whose (it as string contains regex "Linux|AIX|SunOS|HP-UX" )</Relevance>\
        <ActionScript>\
         '+action_script+'\
        </ActionScript>\
        <SuccessCriteria />\
        <Settings />\
        <SettingsLocks />\
        <Target>'
  xml_query=xml_query+""+computer_target+""+"</Target> </SingleAction></BES>"
  xml=xml_query
  #print("\nBuilding Query XML :"+ xml+"\n")
  #sys.exit()
  req_bes_query_id=requests.post(bigfix_URL_action,verify=False,auth=(bigfix_username,bigfix_pwd),data=xml)
  return req_bes_query_id

    comp="<ComputerName>"+x+"</ComputerName>"
     computer_target=comp+""+computer_target

req_bes_query_id=build_xml(computer_target,set)

I don’t really follow what that Python snippet is doing, or how it ends up with the XML you posted in the first message.

It looks like this only appends one single computer_target to xml_query, so how did the sample have three computer targets? I’d expect that structure to loop through a list of computers, something like

xml_query='[the xml_query fragment you started with]'

for computer_target in computer_targets:
    xml_query += f"<ComputerName>{computer_target}</ComputerName>\n"

xml_query += "</Target> </SingleAction></BES>"

I’m also not sure what this area is doing - it comes after the ‘return’ statement so I don’t think it would be executed, but the spacing is messed up too so I’m not sure how it would even run in Python, but maybe that’s just an artifact of the Forum’s markdown spacing.

comp="<ComputerName>"+x+"</ComputerName>"
 computer_target=comp+""+computer_target

In the end, I think you’ll have a lot better results using the actual XML modules, either ‘lxml’ (which requires installing the module through ‘pip’) or ElementTree (which is included in the Python standard library) -

from xml.etree import ElementTree

https://docs.python.org/3/library/xml.etree.elementtree.html

Thank you @JasonWalker for the information and the link. I believe that I have figured out the difficulty I was having and it is with PowerShell and not the BES.xsd. It seems kind of strange to me but I’m sure it has a valid use but it was giving me a headache.

When an element such as <Target> is defined but does not have a value the .dot notation that I’m using seems to think of the element as just a string and not an XML element and throws errors because a string does not have the XML methods I’m attempting to use. For instance the following code will work if I have a <ComputerName> element defined and fail if I do not.

$myXML = [xml](Get-Content .\testActionTemplate.xml)
$newNode = $myXML.CreateElement('ComputerName')
$newNode.InnerXml = "SomeComputer"
$myXML.BES.SourcedFixletAction.Target.AppendChild($newNode)
$myXML.save('.\foobar.xml')

It works fine with this testActionTemplate.xml

<?xml version="1.0" encoding="UTF-8"?>
<BES xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="BES.xsd">
 <SourcedFixletAction>
   <SourceFixlet>
     <Sitename>DevOps Deployment</Sitename>
     <FixletID>99999</FixletID>
     <Action>Action1</Action>
   </SourceFixlet>
   <Target>
    <ComputerName></ComputerName>
   </Target>
 </SourcedFixletAction>
</BES>

But fails when using this

<?xml version="1.0" encoding="UTF-8"?>
<BES xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="BES.xsd">
 <SourcedFixletAction>
   <SourceFixlet>
     <Sitename>DevOps Deployment</Sitename>
     <FixletID>99999</FixletID>
     <Action>Action1</Action>
   </SourceFixlet>
   <Target>
   </Target>
 </SourcedFixletAction>
</BES>

Now that I understand the core of my issue I just need to figure out the best way to work around it. I’m thinking that when I loop through my variable containing the passed in target names and adding them I’ll just remove the “blank” entry.

Thanks again to you and @bigfixforum for your interest and assistance!

Its not the full code. I just picked the part of it, but “build_xml” is complete function.

This is part of the for loop which read from the file to build the “computer_target” variable.

comp=""+x+"“
computer_target=comp+”"+computer_target

1 Like

With the understanding that POSH was treating the trailing empty element as a string I added an empty element to the XML stub I was referencing and then the task became simplistic.

Leaving this code snippet here hoping it might help someone else.

  $stubTemplate = "$PSScriptRoot\testActionTemplate.xml"
  $global:myXML = New-Object xml
  $myXML.Load($stubTemplate)

  foreach ($target in $targetArray) {
    $newNode = $myXML.CreateElement('ComputerName')
    $newNode.InnerXml = $target
    $myXML.BES.SourcedFixletAction.Target.AppendChild($newNode)
  }

Thanks again for your help!

1 Like