I am working on generating (actually editing) a mobileconfig file (aka iOS profile, XML) via bash script.
The script fetch data from a MS Database and has now to inject/replace this data in my mobileconfig file (XML).
The XML file has the following structure:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>PayloadContent</key>
<array>
<dict>
<key>Host</key>
<string>outlook.office365.com</string>
<key>MailNumberOfPastDaysToSync</key>
<integer>7</integer>
<key>Password</key>
<string>ActiveSyncPassword</string>
<key>PayloadDescription</key>
<string>Configures an Exchange account</string>
<key>PayloadDisplayName</key>
<string>Exchange ActiveSync</string>
<key>PayloadVersion</key>
<integer>1</integer>
<key>SSL</key>
<true/>
<key>UserName</key>
<string>xxxxxxx@xxx.com</string>
<key>disableMailRecentsSyncing</key>
<false/>
</dict>
<dict>
<key>AutoJoin</key>
<true/>
<key>EncryptionType</key>
<string>WPA</string>
<key>HIDDEN_NETWORK</key>
<true/>
<key>IsHotspot</key>
<false/>
<key>Password</key>
<string>WEPWPAWPSPEAPTLS</string>
<key>PayloadType</key>
<string>com.apple.wifi.managed</string>
<key>PayloadVersion</key>
<real>1</real>
<key>ProxyType</key>
<string>None</string>
<key>SSID_STR</key>
<string>SSID</string>
</dict>
<dict>
I would like to replace the WiFi Password but also ActiveSync "Password" fields between the < string> < /string> using any native (xmllint, sed) or non-native tool.
Can anyone please help ?
Editing structured data (such as XML) with plain-text tools invariably ends in misery when the file format changes in ways that nobody expects to make a difference (such as inserting benign whitespace). Instead, use a tool that parses XML properly and works on the tree, such as xmlstarlet
.
The general form for this is
xmlstarlet ed -u xpath -v value filename.xml
Where xpath
is an XPath expression that identifies the node you want to update, and value
is the new value you want to give it. The magic is in constructing an XPath expression that uniquely and reliably identifies the node you want to update. The MobileConfig XML format makes this somewhat harder than usual; after discussion in the comments we ended up with
xmlstarlet ed -u '//dict[key[text() = "PayloadDisplayName"]/following-sibling::string[1] = "Exchange ActiveSync"]/key[text() = "Password"]/following-sibling::string[1]' -v 'abc123' filename.xml
The core of this is the XPath expression
//dict[key[text() = "PayloadDisplayName"]/following-sibling::string[1] = "Exchange ActiveSync"]/key[text() = "Password"]/following-sibling::string[1]
..which requires some explanation. We use the following features:
//dict
matches any dict
node in the document,//dict/key
matches any key
node that is the child of a dict
node,//dict/key[text() = "Password"]
matches any key
note that is the child of a dict
node and contains the text Password
,//dict/key[text() = "Password"]/following-sibling
matches any following sibling node of such a key
node, which is to say any node that is a child of the same parent and comes after the key
node in the XML,//dict/key[text() = "Password"]/following-sibling::string
matches any string
node that is such a following sibling node, and//dict/key[text() = "Password"]/following-sibling::string[1]
matches any node that is the first following sibling string
node of such a key
node.We've already used a condition in //dict/key[text() = "Password"]
; in order to find the dict
node whose password entry is to be changed, we need more of that. The dict
node we want to find is identified by
//dict[key[text() = "PayloadDisplayName"]/following-sibling::string[1] = "Exchange ActiveSync"]
That is a dict
node that fulfills the condition
key[text() = "PayloadDisplayName"]/following-sibling::string[1] = "Exchange ActiveSync"
The XPath expressions in this condition are all relative to the dict
node that's being tested, so
key[text() = "PayloadDisplayName"]
refers to a key
subnode of that dict
node that contains the text PayloadDisplayName
, and
key[text() = "PayloadDisplayName"]/following-sibling::string[1] = "Exchange ActiveSync"
is true if the text in the string
node that follows the key
node that contains the text PayloadDisplayName
is Exchange ActiveSync
. So we chuck that into the simplified expression I explained above and get the full filter.
I feel compelled to point out that the structure of this XML file makes the whole thing more difficult than necessary or usual. Sanely structured XML can be handled with much simpler XPath expressions (most of the time).