Showing posts with label XML. Show all posts
Showing posts with label XML. Show all posts

Monday, June 24, 2013

Passing and parsing @ServiceVMComputerNames@

In my past post I mentioned the undocumented Service Settings that SCVMM will automatically fill-in for you and pass to your Application Scripts.

But, how can we pass these?

Some are easy, you just pass them like any other setting since they are relatively short strings.

For example, in testing things for this article I passed @ServiceVMComputerNames@, @ComputerName@, @ServiceName@, @VMID@, and @ServiceID@.  I had no idea how long these might be or what they might look like.

My Test Service had two tiers.  One tier with two VMs, one tier with one VM.  It looks like this:

image

When I deployed the Service I named it “blah”, I have a tier named “The Tier” and another named “The Other Tier”, and three VMs named “xVM01”, “xVM02”, and “yVM01” (SCVMM applied the numbers using the ## notation). 

Within VM xVM01 I sent and captured all of the settings I have mentioned.

What I got out was:

  • @ServiceVMComputerNames@ = “The Tier[xVM01,xVM02]The Other Tier[yVM01]”
  • @ComputerName@ = “xVM01”
  • @ServiceName@ = “blah”
  • @VMID@ = “26fd4a55-a707-4fba-89b5-c6955e4e05a2”
  • @ServiceID@ = “741fbf99-e676-4a8b-9df7-096c0be1fd3e”

These short service names you can safely pass using:  myscript.ps1 –paramName @VMID@ or myscrpt.cmd @VMID@

There are lots of examples about that.

It is @ServiceVMComputerNames@ that could get really long and in turn make the command line too long to execute.  So, this one I pass in a bit differently.  To accommodate the length I pipe the setting to my PowerShell script (as I blogged about here).

In the Service Template designer this looks like:

image

My script receives the object as a pipeline and writes it out.

Within the script:

Param
(
    [parameter(Mandatory = $false, ValueFromPipeline = $true)]
    $serviceNames = ""
)

$logPath = "$env:ProgramData\testPath\"
# write out the string for debugging
$serviceNames | Out-File ($logPath + $startTime.Second + $startTime.Millisecond + ".txt")

Then I can just keep reusing this snip to see what I end up with.

What I in turn do with this input is I parse it into an XML document that I later re-use with other SCVMM Application Scripts.

Param
(
    [parameter(Mandatory = $false, ValueFromPipeline = $true)]
    $serviceNames = ""
)

$logPath = "$env:ProgramData\MyService\"

# make the path so Out-File will be happy
If (!(Test-Path -Path $logPath -PathType Container)){
    New-Item -Path $logPath -ItemType Container -Force
    Start-Sleep 10  # so the OS can register the path
}

# Parse the Service information from SCVMM
$tiers = $serviceNames.Split(']')

$service = New-Object System.Xml.XmlDocument
$root = $service.CreateElement("ServiceTemplate")
$root.SetAttribute("version","1.0")
$root.SetAttribute("createon",(Get-Date))
$root.SetAttribute("createdby","brianeh")

foreach($tierString in $tiers) {

    if($tierString){  #ignore any empties
        $tier = $service.CreateElement("Tier")

        $e = $tierString.Split('[')

            $tier.SetAttribute("Name",$e[0])

            $VMs = $e[1].Split(",")

            foreach ($vmString in $VMs){
                if($vmString){
                     $vm = $service.CreateElement("VM")
                     $vm.SetAttribute("Name",$vmString)
                 }
                $tier.AppendChild($vm)
            }

    }
    $root.AppendChild($tier)
}
$service.AppendChild($root)

$service.Save(($logPath + "ServiceNames.xml"))

There you have it, nice and neat XML.  And your scripts have a clue about themselves.

Thursday, June 13, 2013

Turning a string into an XML document with PowerShell

This is one of those things that always seems like it should be really easy, and straightforward.

Well, if you are deeply familiar with XML it might be.  And If you can pry your string apart into an array using split or regex you have half of the problem tackled.

Lets begin by looking at my string.

$s = "Machine Tier - ScaleOut[Machine03,Machine04,Machine05]Machine Tier[Machine02]"

Ugly thing.  That actually contains three elements of data.

The entire string represents a Service.  The data outside the brackets represents a Tier.  The data inside the brackets is a list of VMs in that Tier.

First, I need to break it all apart.

$a = $s.Split(']');
foreach($b in $a) {
    $c = $b.Split('[');
    "Tier = " + $c[0];
    "VMs = " + $c[1];
}

Tier = Machine Tier - ScaleOut
VMs = Machine03,Machine04,Machine05
Tier = Machine Tier
VMs = Machine02
Tier =
VMs =

Okay, that is close, but not quite there. And still not usable.

First, lets do something about that empty last item in the array.

$a = $s.Split(']');
foreach($b in $a) {
    if($b){
        $c = $b.Split('[');
        "Tier = " + $c[0];
        "VMs = " + $c[1];
    }
}

And now, break apart the VM name string

$a = $s.Split(']');
foreach($b in $a) {
    if($b){
        $c = $b.Split('[');
        "Tier = " + $c[0];
        $VMs = $c[1].Split(",")
        foreach ($vm in $VMs){
            "VM = " + $vm
        }
    }
}

Tier = Machine Tier - ScaleOut
VM = Machine03
VM = Machine04
VM = Machine05
Tier = Machine Tier
VM = Machine02

Okay, now I have something that is usable.  And now I want to turn that into XML.

I have spent a great deal of time searching on PowerShell and XML.  Trying to figure out how to build an XML using PowerShell on the fly in my script.  All the examples always start with a TXT based framework of some type that is in turn manipulated by the author.  Or a file is read, or objects are queried.

I am sorry, but this is not an example of generating an XML using PowerShell as the title suggests.  It is a huge formatted text object that is manipulated.  Such a frustrating example to hit over and over again.

Well, I just have this silly string I parsed apart.  And I want that to be XML.  It already has meaning to the pieces, I just need to make them tags and whatnot.

I mentioned early on that I don’t know much about XML.  I read it, transpose it, consume the XML of others’, I never made my own.  So, I had to visit the wisdom of a developer friend and make sure that i was doing it ‘correctly’ and it was ‘proper’.

In it simplest sense, to create an empty XML document in PowerShell do this: “$service = New-Object System.Xml.XmlDocument” and you have an XML document.  But how do you put things into it.

Okay, this is all about objects, and object manipulation.  You don’t just add tags in.  You create objects that are of the type $service and you add them back to $service in the correct order.

I began with this:

$service = New-Object System.Xml.XmlDocument
$tier = $service.CreateElement("Tier")
$tier.SetAttribute("Name","My Test Tier")

$vm = $service.CreateElement("VM")
$vm.SetAttribute("Name","My VM Name")

$tier.AppendChild($vm)
$service.AppendChild($tier)

I create the XML Document $service.  Then I create an Element of type $service and define a Name and value as an Attribute of that Element.  I repeat this and create a $vm element as well. 

If you query $service, you find that these things aren’t there.  They are three separate objects at this point.  They are all share the same object type of $service.  But nothing more.  Now I assemble them together.

I take the $tier object and I add the $vm object to it as a child.  This nests <vm> under <tier> in the XML.  I then repeat this adding this updated $tier object to $service as a child.

The above is fine enough.  However, I was informed that I was missing a root element.  To define the Document.

$service = New-Object System.Xml.XmlDocument
$root = $service.CreateElement("RootElement")

$tier = $service.CreateElement("Tier")
$tier.SetAttribute("Name","My Test Tier")

$vm = $service.CreateElement("VM")
$vm.SetAttribute("Name","My VM Name")

$tier.AppendChild($vm)
$root.AppendChild($tier)
$service.AppendChild($root)

I have been informed that simply doing what I just showed above is still not quite good enough.  It meets the requirement of a root element but totally missed on the intent or spirit.  We will get back to that.

So, what does this XML document look like?  Well, you can step through $service and try to imagine it in your head our you can send it out to a file and open it in notepad.

$service.Save(".\service.xml")

Open that up and you have:

<RootElement>
  <Tier Name="My Test Tier">
    <VM Name="My VM Name" />
  </Tier>
</RootElement>

Now.  I have some XML.  And I am feeling pretty proud of myself.

Why did I ever do this in the first place?  So I could do this:

PS C:\Users\Public\Documents> $service.GetElementsByTagName("Tier")

Name                                                     VM
----                                                     --
My Test Tier                                             VM

PS C:\Users\Public\Documents> $service.GetElementsByTagName("VM")

Name
----
My VM Name

Now I have associations that I can look for and query against.

Now, the fun part, meshing those two different activities together as one.  And I have the following:

$tiers = $s.Split(']')

$service = New-Object System.Xml.XmlDocument
$root = $service.CreateElement("RootElement")

foreach($tierString in $tiers) {

    if($tierString){  #ignore any empties
        $tier = $service.CreateElement("Tier")

        $e = $tierString.Split('[')

            $tier.SetAttribute("Name",$e[0])

            $VMs = $e[1].Split(",")

            foreach ($vmString in $VMs){
                if($vmString){
                     $vm = $service.CreateElement("VM")
                     $vm.SetAttribute("Name",$vmString)
                 }
                $tier.AppendChild($vm)
            }

    }
    $root.AppendChild($tier)
}
$service.AppendChild($root)

Now, back to that really lazy root element I created.  In practice, that should be some meta information about the XML document itself.  If you look at lots of XML you will see things like creation dates, versions, authors, and a name that is somehow descriptive.

After I create the $root object (with a better name) I just update it with a few attributes and I am good to go.

$root.SetAttribute("version","1.0")
$root.SetAttribute("createon",(Get-Date))
$root.SetAttribute("createdby","brianeh")

Now, a really short example of what I can now do with this information.

# query on a specific VM element with a Name equal to "Machine02"
# The Item(0) returns the XML object itself instead of a reference to it.  $me = $me.ItemOf(0)

$me = ($service.SelectNodes("//VM[@Name='Machine02']")).Item(0)

# What Tier do I belong to?
$me.ParentNode.Name

# Do I have Siblings or am I an only Child?
$me.NextSibling
$me.PreviousSibling
$me.ParentNode.ChildNodes.Count

Note:  be careful.  These queries are in XPath format, and they are case sensitive.

You can also simply walk the XML as PowerShell supports that as a ‘.’ notation path.

Thursday, October 27, 2011

Editing a web.config using PowerShell–adding a node method 2

InnerXML is your friend to add a bunch of XML quickly.

The bit of XML that I want to add to my web.config is:

<location path="FederationMetadata">
    <system.web>
        <authorization>
            <allow users="*" />
        </authorization>
    </system.web>
</location>

The super duper easy way is to simply dump in your XML as text.

Begin by reading in your XML file (just as in the previous posts).

$path = (( Get-Website | where {$_.Name -match ($role.Id)} ).PhysicalPath + "\Mine\Site\web.config")
$file = get-item -path $path

# Get the content of the config file and cast it to XML to easily find and modify settings.

$xml = [xml](get-content $file)

Create the new element object.

$idModel = $xml.CreateElement("microsoft.identityModel")

Add the attribute

 

Populate it with the XML text.

$idModel.Set_InnerXML(
@"
<system.web>
    <authorization>
        <allow users="*" />
    </authorization>
</system.web>

"@
)

Save it back.

$xml.configuration.AppendChild($idModel)

All done.  Super simple, very little tree hopping.

Monday, October 17, 2011

Editing a web.config using PowerShell–changing a value method 2

A few days back I had a post about changing a value of an XML document in which I treated the returned item as and array and simply looped through the array.

I have had a reason to handle things a bit differently.

Lets consider the same bit of XML.

 

However, this time lets treat this like an object.

The first thing is to fetch the XML element in a way that PowerShell keeps it as an element.

I began last time by attempting to get the node using:

$xml.GetElementsByTagName("routingPolicy")

If I use the GetType() method PowerShell returns that it is an XmlElementList.  However, if I try to type dot then tab I cannot walk through the XML. 

This must be because the containing element is actually an XML element in its own right.

Okay I think, then I will get that element in the same way and I try:

$xml.GetElementsByTagName("alternateAddress")

Nothing.  What gives.  I must need to get at this a different way.  Using a little bit of object handling from another XML handling post I tried to treat it as an object.

$altAddr = $xml.GetElementsByTagName("routingPolicy") | where {$_.alternateAddress -match "on"}

If I use the GetType() method on this object I see that I have an XmlElement.  And I can modify its attributes with a simple dot notation.

$altAddr.alternateAddress = "off"

So, I achieved the same result as a few posts ago, but actually handling this as an object instead of as an array.

Before:

$routePolicy = @($xml.GetElementsByTagName("routingPolicy")) 
 
foreach ($e in $routePolicy) { 
     if ($e.alternateAddress -eq "off" ) { 
     $e.alternateAddress = "on" 
     } 
     $e 
}

After:

$altAddr = $xml.GetElementsByTagName("routingPolicy") | where {$_.alternateAddress -match "off"}
$altAddr.alternateAddress = "on"

And that After could probably be changed to a one liner.  I just find many one liners difficult to understand, and someone else will most likely have to figure out what my script does.

Wednesday, October 5, 2011

Editing a web.config using PowerShell–adding a node method 1

This is an extension of a recent bunch of posts about XML and handling XML in PowerShell.

One way of adding a node and content can be accomplished by building the node out of XML objects.  Some might consider the the true ‘object’ way of working through this problem.

The bit of XML that I want to add to my web.config is:

<location path="FederationMetadata">
  <system.web>
    <authorization>
      <allow users="*" />
    </authorization>
  </system.web>
</location>

This is a walkthrough of building that as pure XML objects using PowerShell.  And then inserting it into a very specific location in the larger XML document.

First, I have to load in the web.config and make it an XML document. 

$file = get-item -path $path

$xml = [xml](get-content $file)

Now, Create the ‘location’ node as an XML document.

$federationLocation = $xml.CreateElement("location")

Set the attribute of ‘path’

$federationLocation.SetAttribute("path", "FederationMetadata") 

Create a nested node ‘system.web’

$fedSystemWebNode = $xml.CreateElement("system.web") 

Create a nested node ‘authorization’

$fedAuthNode = $xml.CreateElement("authorization")

Create the ‘allow’ element

$fedAllowNode = $xml.CreateElement("allow")

Add the ‘users’ attribute

$fedAllowNode.SetAttribute("users", "*") 

Now, for the really fun part.  And the important part to pay attention to.

Reassemble the nodes in the correct order

Begin by appending the inner mode node to its parent

$fedAuthNode.AppendChild($fedAllowNode)

Repeat with the next node and its parent

$fedSystemWebNode.AppendChild($fedAuthNode)

Repeat the process again

$federationLocation.AppendChild($fedSystemWebNode) 

Now, I want to insert the XML document Node into a very specific place in the web.config XML configuration file.  That location is before the XML path ‘configuration/system.web’.

$xml.configuration.InsertBefore($federationLocation, ($xml.configuration.Item("system.web")))

That is it.  lots of lines. But performed in a true object style.

Oh, and don’t forget to save back to the file system.

$xml.Save($file.FullName)

Next time, the quick and dirty string method.

Monday, October 3, 2011

Editing a web.config using PowerShell–changing a value

It has been some time since I have been working with XML using PowerShell.  I needed to refresh my brain and work through some issues.

In bunches of searching I ran across only one article that really tries to explain what is going on: http://www.codeproject.com/KB/powershell/powershell_xml.aspx

And coupling this understanding with Tobias Weltner’s chapter on XML starts to put the pieces together:  http://powershell.com/cs/blogs/ebook/archive/2009/03/30/chapter-14-xml.aspx

And, there are clues here: http://devcentral.f5.com/weblogs/Joe/archive/2009/01/27/powershell-abcs---x-is-for-xml.aspx and here: http://blogs.technet.com/b/heyscriptingguy/archive/2010/07/29/how-to-add-data-to-an-xml-file-using-windows-powershell.aspx

I finally sat down with a senior developer, who has always been very helpful, and he finally helped me wrap my brain around manipulating XML using PowerShell.  A bit of shared screen time and he could quickly recognize what PowerShell was doing to the XML document in handling each section.

This has prompted me to attempt to capture this understanding before I get pulled to a new project and I forget it all.

This is part 1 of a series of handling XML in PowerShell.  I decided to tackle something really simple.  Editing a value. 

The ‘partial’ XML kind of way.  I call this partial because I am only using XML handling to find the collection, not to modify it.  But this is a quick trick that I have used quite a bit to modify settings and configuration files.

This is an Azure related example, and in Azure there are no hard paths, so we keep things relative.

# Discover the path of the web.config file

$path = (( Get-Website | where {$_.Name -match ($role.Id)} ).PhysicalPath + "\Mine\Site\web.config")
$file = get-item -path $path

# Get the content of the config file and cast it to XML to easily find and modify settings.

$xml = [xml](get-content $file)

I want to edit the following line in the file:  <routingPolicy alternateFoo="off" />  I want to change the value to “on”

$routePolicy = $xml.GetElementsByTagName("routingPolicy")

This returns an object, $routePolicy.  And looking at that object I see no methods, but it does have a count property.  I actually got a collection back (or an array), not some XML element.

One way to modify this is to go along with this being an array and simply step through it taking advantage of the  dot tag type of XML browsing.

foreach ($e in $routePolicy) {
    if ($e.alternateFoo -eq "off" ) {
    $e.alternateFoo = "on"
    }
    $e
}

That is what I consider the really simple way.  It is quick and dirty and gets the job done.

Now, what I just did is not very “object oriented” which is the power of PowerShell.  So, how do I get in there and change this setting.  What is it really.

The way this setting is defined is that alternateFoo is an attribute of  <routingPolicy />.  It is not a node or an element within it.

Here is where things get really funky.  The following will modify that attribute without having to do the loop.  The important part is that each step of the way we are selecting each node as an individual object and therefore its own XML document within the larger XML document.  The difference here is that I have to know the path.  “routingPolicy” is a node under “system.web”, which is itself a node of the larger “configuration”. 

$xml.configuration.Item("system.web").Item("routingPolicy").SetAttribute("alternateFoo", "on")

Easier to code, but more difficult to understand what is really happening.  It is this stuff that is really not well written out there.

Oh, don’t forget to save the file back to the file system.

$xml.Save($file.FullName)

Thursday, May 5, 2011

PowerShell and XML–bah to overly simplistic examples

Okay, I have been manipulating XML with PowerShell on and off for over a year now.  And I will tell you, all the examples that you find on the internet are way too simplistic.

I have yet to get a piece of XML out of code that is very simple, neat and tidy.

Working with CIM interfaces and XML application configuration files I frequently find my XML muddied up with array strings within tags.

Oh my.  These arrays within an XML Element reek havoc on the nice simple XML parsing that PowerShell does (and assumes).

Or, worse yet, the XML is filled with namespaces and therefore you cannot simply [xml] (cast it to XML) and deal with it.

Say for example that I have a web config file that I want to change, and it is written in XML.  first I have to get the file:

$file = get-childitem -path "IIS:\Sites\Default Web Site\App\Settings\web.config"

Then get the content of the config file and cast it to XML:

$xml = [xml](get-content $file)

Within this file are tags and settings that are actually arrays of settings within tags.  Or they are dictionaries (but they don’t return that way).

In the XML might look like this:

<AppThings allowSomething="on" showBar="off">
  <client />
  <someAppPolicy setting="off" />
  <someOtherAppPolicy enableFoo="off" />
</AppThings>
 

To modify one of these array settings I simply treat it as an array an modify the proper setting.

$somePolicy = @($xml.GetElementsByTagName("someAppPolicy"))

foreach ($e in $somePolicy) {
    if ($e.setting -eq "off" ) {
    $e.setting = "on"
    }
}

At the end you just have to remember to write the XML back to the file:

$xml.Save($file.FullName)