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.

Tuesday, October 25, 2011

IIS PowerShell and Type Mismatch when Set-Item

If you cannot tell I have been doing a lot with PowerShell and web sites (IIS) lately. 

Let’s just say that it is amazing what you can do with an Azure Web Role when you get creative and are not afraid of scripts.  Oh, and since this is Azure, it is IIS 7.

A couple time now I have run into an error with IIS where I am unable to apply settings using Set-Item.

The error I get in return is rather cryptic and gives no leads as to the real culprit:

$dsAppPool | Set-Item
Set-Item : Type mismatch. (Exception from HRESULT: 0x80020005 (DISP_E_TYPEMISMATCH))
At line:1 char:22
+ $dsAppPool | Set-Item <<<<
    + CategoryInfo          : InvalidArgument: (:) [Set-Item], COMException
    + FullyQualifiedErrorId : path,Microsoft.PowerShell.Commands.SetItemCommand

I don’t know about you, but this error does not give me much to go on, well a little; I know I have a type mismatch.

In this specific example I am setting an application pool to run under a user account.

I fetch my username and password into a variable.  These are strings.

But when I do a GetType() against the variable it returns as a String, system.object.  And this is the root of the type mismatch.

A simple cast to string fixes the problem:

$dsAppPool = (Get-ChildItem IIS:\AppPools | where {$_.Name -match ("Authentication")} )
$dsAppPool.processModel.identityType = "SpecificUser"
$dsAppPool.processModel.userName = [string]$siteUser
$dsAppPool.processModel.password = [string]$siteUserPwd
$dsAppPool | Set-Item

The same thing works in other areas of Application Pools as well.  Here I am modifying the default IIS cache flushing behavior (note the timespan declarations):

$mooAppPool = ( Get-ChildItem IIS:\AppPools | where {$_.Name -match ("Moo")} )

$mooAppPool.processModel.idleTimeout = [System.TimeSpan]::FromMinutes(43200)  # Idle Time-Out
$mooAppPool.recycling.periodicRestart.time = [System.TimeSpan]::FromMinutes(432000)  # Regular Time Interval

$mooAppPool | Set-Item

$schPath = $mooAppPool.ItemXPath + "/recycling/periodicRestart"
$p = $mooAppPool.recycling.periodicRestart.schedule.Collection.Count

Do {
    Remove-WebconfigurationProperty $schPath -Name schedule.collection -AtIndex ($p - 1) -Force
    $p--
}
Until ($p -eq "0")

Friday, October 21, 2011

Setting the WinRM HTTPS listener

A recent puzzle of mine has been to configure the HTTPS listener for WinRM.  You might ask; why?  Because it is supported!  And you supposedly can.

This applies to both WinRM and PowerShell remoting.

I say “supposedly” because this has been a messy trip.  And all of the documentation has been of nearly no help, leading me to believe that it is just supposed to work and I must be an idiot for it not working.

Back in 2008 I wrote about securing WinRM using a self signed certificate.  Well, guess what is now blocked.  Using self-signed certificates for WinRM.

So, we move forward, attempting to embrace the security model.

One thing to understand, when using HTTPS with WinRM you must:

  • have a certificate with a CN that matches the hostname of the server
  • use the hostname to connect to the remote machine

In my case I have a private CA that I use, and I simply import the CRL into each server as it is not published to allow CRL lookup.

There has been a litany of errors along the way, far too many for me to attempt to capture.  Lets just say that many have been very obscure and strange failures that have error messages with very little to do with the actual problem.

In the end there are two commands that you want to work on your Server; they are:

winrm create winrm/config/listener?Address=*+Transport=HTTPS @{Hostname=($env:COMPUTERNAME);CertificateThumbprint=($sslCert.Thumbprint)}

or

Set-WSManQuickConfig –useSSL

To get to this point you need to have a certificate authority, and you need to get your certificate on your server.  The CN of your certificate must match the servername (and this is picky too, the case / mixed case must match or you will get an error).  A tip – pay attention to the error message as it has the proper string that it is looking for.

WinRM runs under the NETWORK SERVICE security context.  This is pretty restrictive, so it most likely does not have access to the private key of your certificate.  Because it doesn’t have access it can’t do the encryption and nothing happens.  The setup fails.

Here is the little piece of script that fixes it all up:

$sslCert = Get-ChildItem Cert:\LocalMachine\My | where {$_.Subject -match $env:COMPUTERNAME}

"The installed SSL certificate: " + $sslCert.Subject

###  Give the Network Service read permissions to the private key.

$sslCertPrivKey = $sslCert.PrivateKey

$privKeyCertFile = Get-Item -path "$ENV:ProgramData\Microsoft\Crypto\RSA\MachineKeys\*" | where {$_.Name -eq $sslCertPrivKey.CspKeyContainerInfo.UniqueKeyContainerName}

$privKeyAcl = (Get-Item -Path $privKeyCertFile.FullName).GetAccessControl("Access")

$permission = "NT AUTHORITY\NETWORK SERVICE","Read","Allow"

$accessRule = new-object System.Security.AccessControl.FileSystemAccessRule $permission

$privKeyAcl.AddAccessRule($accessRule)

"Modifying the permissions of the certificate's private key.."

Set-Acl $privKeyCertFile.FullName $privKeyAcl

After you run this, then you can configure the HTTPS listener for WinRM using either of the commands I previously mentioned.

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.

Monday, October 10, 2011

Finding files with PowerShell without ‘and’

There is simply something intrinsically simply about using a selection statement with an “and” in it.

I constantly want to select for a file using a where clause with an '”and” – where just does not like this.  The error that I get each time I try and do this is: “Unexpected token 'and' in expression or statement.”

To get around that I started using a double pipe.

Now, I have to admit, I am not a PowerShell professional.  And many PowerShell one-liners leave me stumped when I try to read and understand what is happening.

Let me try to give you this one.

My goal – find a particular MSI file that exists in a folder.

There could be multiple MSI files, and there could be multiple files with the same name before the extension.  So I have to filter the mess.

I instinctively want to write the line this way:

$msiFile = get-childitem $exPath | where ({$_.Extension -match "msi"} and {$_.Name -match "WindowsIdentityFoundation"})

However, this gives me “unexpected token ‘and’” error all the time.  The quick solution is a double filter accomplished using a pipe and a pipe.

If I write the line this way it works:

$msiFile = get-childitem $exPath | where {$_.Extension -match "msi"} | where {$_.Name -match "WindowsIdentityFoundation"}

What is happening here? 

First is the get-childitem at the literal path $exPath.  Then that gets piped to the MSI filter selecting the extension.  Then that gets piped to the name filter selecting the correct MSI.  The end result gets returned to my variable $msiFile.

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)