preloader
12 April 2011 / #Powercli #Vsphere

Reconnexion automatique en PowerCLI

post-thumb

A un certain moment, nous avons eu des déconnexions répétées sur le vCenter (et relance du service nécessaire), et donc les sessions PowerCLI ouvertes n’étaient plus fonctionnelles.

Lorsque cela arrive une fois à la rigueur, on peut prendre sur soi, mais à partir d’un certains moment il faut réagir ^^ On s’est donc posé la question de la surcharge de cmdlet en Powershell.

Le but initial (départ un peu abstrait je l’avoue) était de trouver un moyen de surcharger le cmdlet Connect-VIServer afin de pouvoir ajouter un bout de code maison.

Après avoir regardé un peu sur le Net, on a vu qu’à la place d’une surcharge de cmdlet, on pouvait plutôt passer par une commande proxy.

Sous PowerShell v2, les métadonnées décrivent des cmdlets, des scripts ou des fonctions. Grâce à cela, on peut connaître les détails internes d’une commande mais aussi interagir avec elle pour écrire des programmes qui manipulent des programmes, appelé aussi la méta programmation.

La v2 de PowerShell permet aussi l’écriture de proxy de cmdlet. Le principe est d’encapsuler une commande, compilée ou pas, afin de modifier son comportement. Par exemple, restreindre les fonctionnalités d’un cmdlet en supprimant un ou plusieurs de ces paramètres, ou dans notre cas, ajouter une fonctionnalité de reconnexion automatique.

Les métadonnées d’une commande sont hébergées, au travers des classes dérivées de CommandInfo, dans un champ d’accès privé de type CommandMetadata.  Il est possible de créer une instance de ce type via la commande suivante :

New-Object System.Management.Automation.CommandMetaData( Get-Command Connect-VIServer )

connectauto_03

Le côté fun de cette instance (et surtout ce qui nous intéresse) est qu’elle peut être utilisée pour créer une commande basée sur une autre commande (d’où le nom de proxy).

Voici un exemple pour le cmdlet Connect-VIServer (c’est le constructeur de la classe ProxyCommand qui s’occupe du boulot) :

$Meta = New-Object System.Management.Automation.CommandMetaData( Get-Command Connect-VIServer )
[System.Management.Automation.ProxyCommand]::create($Meta)

connectauto_04

Le code généré représente la *proxysation* du cmdlet Connect-VIServer.

En l’état, le code généré ne permet pas de faire quoi que ce soit de plus que ce que fait le cmdlet de base. Cependant, on peut tout à fait rajouter du code afin d’ajouter/enlever une fonctionnalité, dans notre cas, la reconnexion automatique au vCenter en PowerCLI.

Pour cela, on veut rajouter un paramètre, nommons le -AutoReco qui ne prend pas d’argument (donc de type Switch) qui lorsqu’il est utilisé effectuera la reconnexion automatique sans préciser le serveur ni les credentials.

Il faut donc que l’on définisse ce nouveau paramètre (et qu’on l’insère dans les paramètres existants) :

[Parameter(ParameterSetName='Default')]
[Switch]
${AutoReco}

Afin de modifier le comportement du cmdlet, PowerShell propose un mécanisme d’accès au pipeline de la commande concernée aka le steppable pipeline. Un steppable pipeline relie le pipeline de la commande encapsulée au pipeline du code de votre proxy et permet de contrôler les blocs Begin, Process et End de la commande et donc de pouvoir rajouter/enlever des fonctionnalités.

Le principe de la commande proxy va se décomposer en 4 étapes :

Récupération des métadonnées de la commande

$wrappedCmd = $ExecutionContext.InvokeCommand.GetCommand('Connect-VIServer', [System.Management.Automation.CommandTypes]::Cmdlet)

Création d’un Scriptblock dont le contenu exécutera la commande en lui passant les paramètres liés

$scriptCmd = {& $wrappedCmd @PSBoundParameters }

Récupération de l’accès au mécanisme du pipeline de la commande

$steppablePipeline = $scriptCmd.GetSteppablePipeline($myInvocation.CommandOrigin)

Configuration de la commande avec les informations de contexte d’exécution de notre fonction

$steppablePipeline.Begin($PSCmdlet)

Pour revenir à notre cas, nous souhaitions mettre en place une reconnexion automatique. Pour cela, il faut que l’on stocke les informations d’authentification (credentials) et le serveur vCenter.

La commande proxy que nous voulions faire réagit comme cela :

Si le paramètre -AutoReco n’est pas présent, on effectue une connexion normale en conservant les informations souhaitées (credentials + vCenter)

Si le paramètre -AutoReco est présent, on force une déconnexion du client PowerCLI et on effectue une reconnexion avec les informations conservées (credentials + vCenter)

Nous effectuons cela en intervenant sur les paramètres de notre commande proxy.

Par exemple, on utilise [Void]$PSBoundParameters.Remove(Credential) pour bypasser l’utilisation de ce paramètre de notre commande proxy puisque nous pouvons le pousser directement lors de l’appel via $scriptCmd = {& $wrappedCmd @PSBoundParameters -Credential $global:credVC }

if ($AutoReco.isPresent)
{
    Disconnect-VIServer -Server $global:serverVC -confirm:$false -ErrorAction:SilentlyContinue | Out-Null
    [Void]$PSBoundParameters.Remove("AutoReco")
    [Void]$PSBoundParameters.Remove("Credential")
    [Void]$PSBoundParameters.Remove("Server")
    $scriptCmd = {& $wrappedCmd @PSBoundParameters -Credential $global:credVC -Server $global:serverVC }
}
else
{
    $global:credVC = $host.ui.PromptForCredential("Authentification nécessaire", "Veuillez entrer les informations de connexion au vSphere Center.", "", "")
    $global:serverVC = $Server
    [Void]$PSBoundParameters.Remove("Credential")
    $scriptCmd = {& $wrappedCmd @PSBoundParameters -Credential $global:credVC }
}

On peut ainsi jouer avec les arguments pour rajouter/enlever des fonctionnalités ou même bloquer un comportement trop permissif d’un cmdlet (par exemple en désactivant l’accès à un paramètre -force permettant d’écraser un fichier, pratique dans le cas de délégation poussée). En fait on peut faire vraiment ce que l’on veut (c’est ça qui est bien ^^).

Voici pour finir le code complet de la commande proxy du cmdlet Connect-VIServer :

function Connect-VIServer()
{
    [CmdletBinding(DefaultParameterSetName='Default')]
    param(
        [Parameter(ParameterSetName='Default', Position=1)]
        [ValidateNotNullOrEmpty()]
        [System.String[]]
        ${Server},

        [Parameter(ParameterSetName='Default')]
        [ValidateRange(0, 65535)]
        [ValidateNotNull()]
        [System.Int32]
        ${Port},

        [Parameter(ParameterSetName='Default')]
        [ValidateSet('http','https')]
        [System.String]
        ${Protocol},

        [Parameter(ParameterSetName='Default', ValueFromPipeline=$true)]
        [ValidateNotNullOrEmpty()]
        [System.Management.Automation.PSCredential]
        ${Credential},

        [Parameter(ParameterSetName='Default', ValueFromPipeline=$true)]
        [Alias('Username')]
        [System.String]
        ${User},

        [Parameter(ParameterSetName='Default')]
        [System.String]
        ${Password},

        [Parameter(ParameterSetName='Default')]
        [System.String]
        ${Session},

        [Parameter(ParameterSetName='Default')]
        [Switch]
        ${NotDefault},

        [Parameter(ParameterSetName='Default')]
        [Switch]
        ${SaveCredentials},

        [Parameter(ParameterSetName='Default')]
        [Switch]
        ${AutoReco},

        [Parameter(ParameterSetName='Menu', Mandatory=$true)]
        [Switch]
        ${Menu})

begin
{
    try {
        $outBuffer = $null
        if ($PSBoundParameters.TryGetValue('OutBuffer', [ref]$outBuffer)
        {
            $PSBoundParameters['OutBuffer'] = 1
        }
        $wrappedCmd = $ExecutionContext.InvokeCommand.GetCommand('Connect-VIServer', [System.Management.Automation.CommandTypes]::Cmdlet)

        if ($AutoReco.isPresent)
        {
            Disconnect-VIServer -Server $global:serverVC -confirm:$false -ErrorAction:SilentlyContinue | Out-Null
            [Void]$PSBoundParameters.Remove("AutoReco")
            [Void]$PSBoundParameters.Remove("Credential")
            [Void]$PSBoundParameters.Remove("Server")
            $scriptCmd = {& $wrappedCmd @PSBoundParameters -Credential $global:credVC -Server $global:serverVC }
        }
        else
        {
            $global:credVC = $host.ui.PromptForCredential("Authentification nécessaire", "Veuillez entrer les informations de connexion au vSphere Center.", "", "")
            $global:serverVC = $Server
            [Void]$PSBoundParameters.Remove("Credential")
            $scriptCmd = {& $wrappedCmd @PSBoundParameters -Credential $global:credVC }
        }
        $steppablePipeline = $scriptCmd.GetSteppablePipeline($myInvocation.CommandOrigin)
        $steppablePipeline.Begin($PSCmdlet)
    } catch {
        throw
    }
}

process
{
    try {
        $steppablePipeline.Process($_)
    } catch {
        throw
    }
}

end
{
    try {
        $steppablePipeline.End()
    } catch {
        throw
    }
}
<# .ForwardHelpTargetName Connect-VIServer .ForwardHelpCategory Cmdlet #>

}

Pour que cela soit le plus transparent possible, on va modifier certaines choses. Tout d’abord, on va enregistrer le code ci-dessous dans un fichier _Proxy_Connect-VIServer.ps1 Ensuite, on va éditer le contexte d’instanciation du PowerCLI. En regardant le lancement du raccourcis de PowerCLI, on obtient la chaîne suivante :

C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe -psc "C:\Program Files (x86)\VMware\Infrastructure\vSphere PowerCLI\vim.psc1" -noe -c ". \"C:\Program Files (x86)\VMware\Infrastructure\vSphere PowerCLI\Scripts\Initialize-PowerCLIEnvironment.ps1\""

On va donc modifier le fichier Initialize-PowerCLIEnvironment.ps1 pour rajouter notre fonction en rajoutant un appel à notre fichier :

...
set-alias Get-VIToolkitConfiguration Get-PowerCLIConfiguration
set-alias Set-VIToolkitConfiguration Set-PowerCLIConfiguration

# Import des custom Functions
. "C:\_Perso\_Proxy_Connect-VIServer.ps1"

# SIG # Begin signature block
# MIIaZAYJKoZIhvcNAQcCoIIaVTCCGlECAQExCzAJBgUrDgMCGgUAMGkGCisGAQQB
# gjcCAQSgWzBZMDQGCisGAQQBgjcCAR4wJgIDAQAABBAfzDtgWUsITrck0sYpfvNR
...

Ce qui est top, c’est que vu que notre fonction possède le même nom que le cmdlet classique, il sera prioritaire, donc maintenant un appel à Connect-VIServer appellera notre commande proxy et non plus le cmdlet classique (et +1 pour la transparence).

Au final, on obtient cela :

connectauto_02

  • Etape 1 : Connexion classique en PowerCLI à un vCenter, en fait, l’appel fait passe par notre commande proxy
  • Etape 2 : Connexion effectuée au vCenter
  • Etape 3 : Vérification de la connexion en exécutant une commande
  • Etape 4 : Simulation de la perte de connexion en faisant un Terminate Session dans le vCenter (raccourcis Ctrl+Shift+S)
  • Etape 5 : Vérification de la perte de connexion en ré-exécutant la même commande qu’en étape 3
  • Etape 6 : Lancement de la reconnexion via l’argument -AutoReco (aucune demande d’informations, les données d’authentification et les informations d’accès au vCenter sont reprises depuis l’étape 1)
  • Etape 7 : Vérification de la reconnexion en exécutant une commande

Note : Cela ne représente qu’un exemple de ce que l’on peut faire (et surtout nous permettant de manipuler les ProxyCommand), on pourrait aller bien plus loin en prenant en compte l’authentification intégrée, la gestion de plusieurs connexions simultanée depuis un même client, etc!


> Frederic MARTIN