Saturday, May 9, 2009

Create a server form with PowerShell 2 & Word 2007 (Part 3)

Previous part of this post is HERE !

Now we get remote server information, we can create our Word 2007 template by hand. By hand ? Why ? Because our script will refresh some parts of this document. So, the script will be independant of the template and we will be able to created a template with our company logo and colors.

PowerShell will write in parts of the document. Theses parts will be called Content Control ! We can insert a content control in the Word 2007 « developer » tab and the « Controls » group:

For each information we get in the previous post (using our PowerShell module), we will insert a content control in our document.

When a content control is inserted, we can specify a name. Our PowerShell script will be able to choose the part of the document to complete matching names of content controls and object properties names.

In the previous part of this post, we wrote some functions: Get-ComputerHardware and Get-ComputerSoftware. Theses functions return a custom object: each property is an information extracted from the remote computer.

The tips is to use the same name for content controls and object properties.

After having created the document with our company's colors and inserted content controls... We have to write the script to refresh the document which means write into content control !

To do that, in a new file named « Generate-ServerForm.ps1 », write the following function:

  1. function Refresh-OpenXmlDocument($strDocPath, $strComputer)
  2. {
  3.     $objDoc = Get-OpenXmlDocument -Path $strDocPath -SuppressBackups
  4.     if ($objDoc -eq $null) { return }

  5.     $objUri = New-Object Uri('/word/document.xml', [UriKind]::Relative)
  6.     $objPart = $objDoc.Package.GetPart($objUri)

  7.     $script:objXmlDoc = New-Object Xml.XmlDocument
  8.     $script:objXmlDoc.Load($objPart.GetStream())

  9.     $objNsMgr = New-Object Xml.XmlNamespaceManager($objXmlDoc.NameTable)
  10.     $strURL = ''
  11.     $objNsMgr.AddNamespace('w', "$strURL/wordprocessingml/2006/main")

  12.     $objNodes = $script:objXmlDoc.DocumentElement.GetElementsByTagName('w:sdt')
  13.     $objInfo = Get-ComputerInfo $strComputer

  14.     foreach ($objNode in $objNodes) {
  15.         $objN = $objNode.SelectSingleNode('w:sdtPr', $objNsMgr)
  16.         $strAlias = Get-OpenXmlContentCtrlAlias $objN $objNsMgr
  17.         if ($strAlias -ne '') {
  18.             $objProperty = $objInfo | Get-Member -Name $strAlias
  19.             if ($objProperty -ne $null) {
  20.                 $objN = $objNode.SelectSingleNode('w:sdtContent', $objNsMgr)
  21.                 switch -regex ($objInfo.$strAlias.GetType().Name) {
  22.                     '\[\]$' {
  23.                         Set-OpenXmlTable $objN $objNsMgr $objInfo.$strAlias
  24.                     }
  25.                     default {
  26.                         Set-OpenXmlText $objN $objNsMgr $objInfo.$strAlias
  27.                     }
  28.                 }
  29.             }
  30.         }
  31.     }

  32.     $script:objXmlDoc.Save($objPart.GetStream())
  33.     $objDoc.Close()
  34. }

It's time to analyse the function !

The main goal is to write into content controlsof the Word document. This function uses 2 parameters :
  • Full path to the Word document (template)
  • Remote server name : our server form will gather information about this server
For now, let's go inside the OpenXML format: don't be afraid, that's an easy part !

First, have a look to the Get-OpenXMLDocument Cmdlet. It comes from PowerTools. So, to use it you have to install theses tools as described in this post!

As an introduction to the OpenXML format: since Office 2007, documents are ased on an opened format (XML) and compressed in a ZIP archive. In fact, if you renamed a DOCX file into a ZIP file you can decompress it!

Using Word 2007, you will have a « /word/document.xml » file used to store the main content of the Word document. It is this file we will modify searching the « w:sdt » tag which is a content control!

  1. $script:objXmlDoc.DocumentElement.GetElementsByTagName('w:sdt')

here we used $script:objXmlDoc so we can access to this variable all over the script.

We can jump from a content control to an other just searching the specified tag! Then, for each tag found, we have to get its name which is also the name of the custom object property previously built by Get-ComputerInfo. This last function merges objects built by Get-ComputerHardware and Get-ComputerSoftware functions.

To do that, you can add the following code to the PowerShell module:

  1. function Merge-Object($objSource, $objDest)
  2. {
  3.     $objSource | Get-Member -MemberType NoteProperty | % {
  4.         Add-Property $objDest $_.Name $objSource.($_.Name)
  5.     }
  7.     return $objDest
  8. }

  9. function Get-ComputerInfo($strComputer)
  10. {
  11.     $objHardware = Get-ComputerHardware $strComputer
  12.     $objSoftware = Get-ComputerSoftware $strComputer
  14.     return (Merge-Object $objHardware $objSoftware)
  15. }

Merge-Objet function merges customized objects.

Don't forget to export our new function:

  1. Export-ModuleMember -Function 'Get-ComputerHardware',
  2.                               'Get-ComputerSoftware',
  3.                               'Get-ComputerInfo'

Well, we need one more thing: our script have to use our module! This can be done using Import-Module Cmdlet. So, our script have to start with the following command:

  1. Import-Module .\Computer_Info.psm1

Here, our module is located in the script directory.

Finally, for each content control, we gets its name using Get-OpenXmlContentCtrlAlias function:

  1. function Get-OpenXmlContentCtrlAlias($objNode, $objNsMgr)
  2. {
  3.     $strAlias = ''

  4.     $objNode = $objNode.SelectSingleNode('w:alias', $objNsMgr)
  5.     $strAlias = $objNode.Attributes | where-Object { $_.Name -eq 'w:val'}
  6.     if ($strAlias -ne $null) { $strAlias = $strAlias.Value }
  7.     else { $strAlias = '' }
  9.     return $strAlias.Trim()
  10. }

There are two cases: if we have to write information in a table then Set-OpenXmlTable is called else Set-OpenXmlText is called!

Since we done a lot of works, it's time to have a break!
See you soon!

No comments:

Post a Comment