jeudi 26 février 2009

PowerShell 2 et Word 2007 : Génération de fiches serveur

Nous y voilà, nous pouvons générer une fiche serveur en 4 étapes :
  • Partie 1 : Installation et configuration des pré-requis.
  • Partie 2 : Création du module PowerShell collectant les informations sur une machine distante.
  • Partie 3 : Création du document Word 2007 (template).
  • Partie 4 : Génération de la fiche serveur.

Vous trouverez l'ensemble des sources en libre téléchargement ici : Generate-ServerForm.zip.

Ces sources sont à titre d'exemple, libre à vous d'améliorer les scripts ! Il y a beaucoup de choses à faire... :)

vendredi 20 février 2009

Création de fiche serveur avec Word 2007 et PowerShell (Partie 4)

La partie précédente de cet article se trouve ICI !

Nous arrivons à la dernière partie. Nous pourrons donc enfin avec PowerShell générer une fiche serveur !

Il ne nous reste plus qu’à écrire les fonctions permettant de mettre à jour du texte dans un document Word.

function Set-OpenXmlText($objNode, $objNsMgr, $strData)
{
    $blnIsCheckBox = $false
    $objNodeT = $null
    $objNodeR = $null

    $objNodeTmp = $objNode.SelectSingleNode('w:tc', $objNsMgr)
    if ($objNodeTmp -ne $null) { $objNode = $objNodeTmp }
    $objNodeTmp = $objNode.SelectSingleNode('w:p', $objNsMgr)
    if ($objNodeTmp -ne $null) { $objNode = $objNodeTmp }
    $objNodes = $objNode.SelectNodes('w:r', $objNsMgr)
    foreach ($objNodeR in $objNodes) {
        $objNodeSYM = $objNodeR.SelectSingleNode('w:sym', $objNsMgr)
        $blnIsCheckBox = $objNodeSYM -ne $null
        if ($blnIsCheckBox) {
            $objAttr = $objNodeSYM.Attributes |
                       where-Object { $_.Name -eq 'w:char'}
            $objAttr.Value = 'F0A8'
            if ($strData -eq $true) { $objAttr.Value = '00FE' }
            break
        }
        $objNodeT = $objNodeR.SelectSingleNode('w:t', $objNsMgr)
        if ($objNodeT -ne $null) { $objNodeT.InnerText = '' }
        # Sinon, il faut créer une balise W:T sous $objNodeR
    }
    if ((-not $blnIsCheckBox-And ($objNodeT -ne $null)) {
        $objNodeT.InnerText = $strData
    }
}

Le but n’est pas d’étudier de près le format OpenXML… Mais nous avons besoin de quelques notions pour bien comprendre la fonction Set-OpenXmlText. Rien de bien difficile je vous rassure !

Pour cela, nous allons essayer de comprendre ce qui se passe lorsque nous insérons ce contrôle de contenu :


Nous avons vu que lorsque nous modifions notre document Word, nous modifions en faite le fichier /word/document.xml de notre package DOCX. Plus précisément, nous ajoutons dans notre cas les balises suivantes :


Notre contrôle de contenu est représenté par la balise w:sdt. Cette dernière se découpe en deux parties mises en évidence par deux couleurs différentes.

La partie verte (balise w:sdtPr) contient les propriétés de notre contrôle. Ainsi on va pouvoir y trouver son nom. Dans l’exemple, notre contrôle a pour nom FctNetworkInfrastructure. Ce nom est récupéré par la fonction Get-OpenXmlContentCtrlAlias que nous avons vu dans l’article précédent !

La fonction Set-OpenXmlText que nous avons découvert dans cet article va modifier la partie bleue (balise w:sdtContent) qui représente le contenu de notre contrôle de contenu !

Rien de compliqué… non ?!

Je mettrai à votre disposition les sources complètes et commentées de la génération d’une fiche serveur avec PowerShell et Word 2007 très bientôt dans un autre post ; avec des exemples d’utilisation !

vendredi 13 février 2009

Création de fiche serveur avec Word 2007 et PowerShell (Partie 3)

La partie précédente de cet article se trouve ICI !

Nous avons donc à ce stade récupéré les informations matérielles et logicielles d’une machine distante.

Nous allons donc maintenant créer un document Word 2007 à la main. Pourquoi à la main ? Parce que notre script sera capable de rafraîchir certaines « zones » de notre document. De ce fait, il s’adaptera au template imposé par notre société !

Les « zones » dans lesquelles PowerShell écrira sont en réalité appelées « Contrôles de contenu » et se trouvent dans l’onglet « Développeur » de Word 2007 puis dans le groupe « Contrôles ». Comme dans l’image ci-dessous :


Ainsi, pour chaque information récupérée dans l’article précédent, nous allons insérer dans notre document un contrôle de contenu.

Lorsqu’un contrôle de contenu est inséré, nous pouvons lui donner un nom et nous allons le faire ! Cela permettra à notre script PowerShell qui écrira dans le document Word de savoir dans quel contrôle de contenu écrire en fonction de l’information récupérée sur notre machine distante.


Dans la partie précédente de l’article, nous avons écrit les fonctions Get-ComputerHardware et Get-ComputerSoftware. Si vous avez regardé de prêt ces fonctions, elle retourne un objet dont les propriétés sont les informations récupérées sur la machine distante.

L’astuce est donc de donner à nos contrôles de contenu les mêmes noms que les propriétés de nos objets retournés par les deux fonctions citées précédemment !

Une fois le document créé aux couleurs de notre société et peuplé de nos contrôles de contenu… nous pouvons créer le script qui va rafraîchir ce document : c'est-à-dire écrire dans les contrôles de contenu !

Pour cela, dans un nouveau fichier nommé « Generate-ServerForm.ps1 » nous allons écrire :

  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 = 'http://schemas.openxmlformats.org'
  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. }

Nous allons essayer d’analyser cette première fonction. Pour commencer… son but est d’écrire dans les contrôles de contenu d’un document Word 2007. Elle prend deux arguments ; le premier : le chemin vers notre template précédemment créé et le second, le nom de la machine dont nous devons récupérer les informations pour faire notre fiche.

Ensuite, nous allons mettre un peu les mains dans le format OpenXML !

La première chose qu’on remarque est l’appel à la Cmdlet Get-OpenXMLDocument. Elle provient des PowerTools. Vous devez donc avoir installé ces outils comme décrit dans cet article !

Nous allons faire une petite parenthèse sur le format OpenXML : un fichier Office 2007 est en réalité un fichier .ZIP renommé en .DOCX, XLSX ou PPTX ! Si vous renommez le fichier avec l’extension .ZIP et que vous le décompressez vous aurez une arborescence de fichiers.

Pour Word, vous aurez notamment un fichier « /word/document.xml » qui n’est rien d’autre que le contenu de notre document Word 2007 au format XML ! C’est précisément ce fichier XML que nous récupérons pour le manipuler. Fin de la parenthèse sur le format OpenXML !

Nous allons donc nous déplacer dans ce fichier XML en repérant nos contrôles de contenu dont la balise est : « w:sdt »

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

Remarque : nous avons écrit $script:objXmlDoc afin de rendre visible notre variable dans tout le script.

Nous pouvons donc naviguer de contrôles de contenu en contrôles de contenu dans notre document en repérant ces balises ! Ensuite, pour chaque contrôle de contenu, nous devons récupérer son nom qui je vous le rappelle est aussi le nom d’une propriété de l’objet retourné par nos fonctions récupérant les informations d’une machine. Dans notre exemple, nous avons utilisé une fonction nommée Get-ComputerInfo. Cette fonction retourne un objet fusionnant le retour de Get-ComputerHardware et Get-ComputerSoftware.

Pour cela vous pouvez rajouter cette fonction dans notre module précédemment créé :

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

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

Remarque : la fonction Merge-Objet fusionne les propriétés que nous avons rajoutées sur nos objets !

Biensûr, il faut modifier dans ce cas la fin de notre module pour rendre visible cette nouvelle fonction :

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

Mais nous avons oublié une petite chose ! Nous devons dire à notre nouveau script qu’il doit utiliser notre module ! Nous le faisons grâce à la Cmdlet Import-Module que nous ajoutons au tout début de notre script :

  1. Import-Module .\Computer_Info.psm1

Remarque : ici, notre module se situe dans le même répertoire que notre script.

Maintenant que nous avons tous les éléments, pour chaque contrôle de contenu nous récupérons son nom avec la fonction Get-OpenXmlContentCtrlAlias :

  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 = '' }
  8.     
  9.     return $strAlias.Trim()
  10. }

Puis, si les données que nous devons écrire sont contenues dans un tableau nous appelons Set-OpenXmlTable sinon nous appelons Set-OpenXmlText !

Comme nous avons bien avancé, nous allons faire une petite pause ! Je vous donne rendez-vous au prochain article pour la suite de notre petite aventure !

samedi 7 février 2009

Création de fiche serveur avec Word 2007 et PowerShell (Partie 2)

La partie précédente de cet article se trouve ici !
Nous voici à la première étape : Identification et récupération des informations pertinentes sur nos serveurs !

Dans cet article nous allons créer un module PowerShell capable de récupérer les informations des serveurs de notre parc informatique. Mais… c’est quoi un module ? C’est tout simplement un ensemble de fonctions : une sorte de librairie que nous pouvons réutiliser et partager !

Remarque : Les modules sont apparus avec PowerShell V2.

Pour faire notre premier module, nous allons créer un fichier nommé « Computer_Info.psm1 ». Remarquez l’extension .PSM1 du fichier ! La seconde chose à faire est d’y écrire nos fonctions qui iront chercher les informations de nos serveurs.

Ensuite, nos futurs scripts pourront faire appel aux fonctions de ce module. Vous l’aurez compris, les modules permettent de réutiliser le code, de faire des librairies ; bref de capitaliser !

Par défaut, PowerShell ira chercher nos modules dans les répertoires listés dans la variable d’environnement PSMODULEPATH ainsi que dans le répertoire courant du script faisant appel à un module. Pour afficher cette variable, exécutez la commande :

PS> $env:PSMODULEPATH

Nous pouvons donc commencer à récupérer les informations pertinentes de nos serveurs. Nous allons pour cela utiliser plusieurs méthodes. La première : WMI !
Cette première fonction que nous allons écrire dans notre module est une petite surcouche à la Cmdlet PowerShell Get-WmiObject. Elle nous permettra uniquement d’économiser quelques lignes de code…

  1. function Get-WmiInfo($strClass, $strComputer)
  2. {
  3.     return Get-WmiObject -Class $strClass -Namespace 'root\cimv2' `
  4.                          -ComputerName $strComputer
  5. }

Nous allons également ajouter une autre petite fonction qui nous facilitera la vie…

  1. function Add-Property($obj, $strProperty, $objValue)
  2. {
  3.     Add-Member -InputObject $obj -Type 'NoteProperty' -Name $strProperty `
  4.                -value $objValue -force
  5. }

La fonction Add-Property est une surcouche à la Cmdlet Add-Member permettant ici d’ajouter une propriété à un objet. Vous comprendrez pourquoi nous l’utiliserons un peu plus bas dans cet article !

Maintenant que nous avons écrit nos deux fonctions « outil », nous pouvons dans un premier temps récupérer les informations matérielles de nos machines :

  1. function Get-ComputerHardware($strComputer)
  2. {
  3.     $objRes = New-Object Object
  4.     $int = 0
  5.     
  6.     # Processors
  7.     $obj = Get-WmiInfo 'Win32_Processor' $strComputer
  8.     foreach ($objInfo in $obj) {
  9.         Add-Property $objRes 'Is32Bit' ($objInfo.Architecture -eq 0x0)
  10.         Add-Property $objRes 'Is64Bit' ($objInfo.Architecture -eq 0x9)
  11.         $int++
  12.     }
  13.     Add-Property $objRes 'NbCPU' $int
  14.     
  15.     # BIOS
  16.     $obj = Get-WmiInfo 'Win32_Bios' $strComputer
  17.     Add-Property $objRes 'BIOS' $obj.Name
  18.     
  19.     # System
  20.     $obj = Get-WmiInfo 'Win32_ComputerSystem' $strComputer
  21.     $int = [int]($obj.TotalPhysicalMemory / 1024 / 1024 / 1024)
  22.     Add-Property $objRes 'TotalMemory' "$int Go"
  23.     Add-Property $objRes 'Manufacturer' $obj.Manufacturer
  24.     Add-Property $objRes 'Model' $obj.Model
  25.     Add-Property $objRes 'Domain' $obj.Domain
  26.     Add-Property $objRes 'DNSName' ($obj.Name + '.' + $obj.Domain)
  27.     Add-Property $objRes 'NetBIOSName' $obj.Name
  28.     
  29.     # Media
  30.     $obj = Get-WmiInfo 'Win32_CDROMDrive' $strComputer
  31.     foreach ($objInfo in $obj) {
  32.         switch ($objInfo.MediaType)
  33.         {
  34.             'CD-ROM' { Add-Property $objRes 'MediaCD' $true }
  35.             'DVD-ROM' { Add-Property $objRes 'MediaDVD' $true }
  36.             'CD Writer' { Add-Property $objRes 'MediaCDRW' $true }
  37.             'DVD Writer' { Add-Property $objRes 'MediaDVDRW' $true }
  38.             default { }
  39.         }
  40.     }
  41.     
  42.     # Physical or virtual
  43.     $obj = Get-WmiInfo 'Win32_BaseBoard' $strComputer
  44.     $bln = ($obj.Manufacturer.Tolower() -contains 'microsoft'-Or
  45.            ($obj.Manufacturer.Tolower() -contains 'vmware')
  46.     Add-Property $objRes 'IsVirtual' $bln
  47.     Add-Property $objRes 'IsPhysical' (-not $bln)
  48.     
  49.     # Network adapters
  50.     Set-StrictMode -Off
  51.     $obj = Get-WmiInfo 'Win32_NetworkAdapter' $strComputer |
  52.            Where-Object { $_.PhysicalAdapter -eq $true } |
  53.            Select-Object ProductName, MACAddress
  54.     Set-StrictMode -Version 2.0
  55.     Add-Property $objRes 'NetworkCards' $obj
  56.     
  57.     # Disks
  58.     $obj = Get-WmiInfo 'Win32_DiskDrive' $strComputer |
  59.            Select-Object Model, Size
  60.     Add-Property $objRes 'Disks' $obj
  61.     
  62.     # Volumes
  63.     $obj = Get-WmiInfo 'Win32_LogicalDisk' $strComputer |
  64.            Where-Object { $_.DriveType -eq 3 } |
  65.            Select-Object VolumeName, DeviceID, Size, FreeSpace
  66.     Add-Property $objRes 'Volumes' $obj

  67.     return $objRes
  68. }

La fonction Get-ComputerHardware retourne un objet dont les propriétés sont les informations matérielles d’un ordinateur récupérées par différentes requêtes WMI !

Nous pouvons également récupérer des informations logicielles :

  1. function Get-ComputerSoftware($strComputer)
  2. {
  3.     $objRes = New-Object Object
  4.     
  5.     # Operating system
  6.     $obj = Get-WmiInfo 'Win32_OperatingSystem' $strComputer
  7.     Add-Property $objRes 'OSName' $Obj.Caption
  8.     Add-Property $objRes 'OSVersion' $Obj.Version
  9.     Add-Property $objRes 'OSServicePack' $obj.CSDVersion
  10.     Add-Property $objRes 'OSArchitecture' $obj.OSArchitecture
  11.     
  12.     # Services
  13.     $obj = Get-Service -ComputerName $strComputer |
  14.            Select-Object DisplayName, ServiceName, Status
  15.     Add-Property $objRes 'Services' $Obj
  16.     
  17.     # KB
  18.     $obj = Get-WmiInfo 'Win32_QuickFixEngineering' $strComputer |
  19.            Sort-Object InstalledOn |
  20.            Select-Object Description, HotFixID, InstalledBy, InstalledOn
  21.     Add-Property $objRes 'KB' $Obj
  22.     
  23.     return $objRes
  24. }

Remarquez ici l’utilisation de la Cmdlet Get-Service avec le paramètre –ComputerName qui existe depuis la V2 !

Maintenant que nous avons récupéré les informations matérielle et logicielle, nous avons presque terminé notre module !

Vous avez remarqué que nous avons écrit des fonctions « outil ». Nous aimerions donc que ces fonctions ne soient pas utilisables dans des scripts car sorties de leur contexte elles peuvent être mal utilisées.

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

La Cmdlet Export-ModuleMember nous permet d’exposer uniquement nos deux principales fonctions de notre module. Ainsi, les scripts qui utiliseront notre module ne pourront se servir que de ces deux fonctions !

Nous avons donc dans cet article :
1. Créé un module PowerShell V2,
2. Récupéré les informations matérielles et logicielles d’une machine.

Dans le prochain article, nous créerons notre document Word 2007 qui sera utilisé pour générer nos fiches serveurs !