Jacques's profilejanelPhotosBlogLists Tools Help
    August 28

    La RC2 de PowerShell disponible en français

    Si vous suivez le forum Microsoft dédié à Windows PowerShell, vous avez peut-être relevé une réponse de Jeffrey Snover, architecte de PowerShell, à une question posée tôt ce matin :
     
    Q. Are there any localized version of PowerShell? When will be ready?
    R. RC2 is localized. We have not announced a public date for its release.
     
    Même si votre anglais est approximatif vous l’aurez sans doute compris : la RC2 sera « localisée » - terme barbare pour désigner la traduction dans nos langues locales à nous autres, pauvres non-Américains. J’ai le plaisir de tester PowerShell en français depuis plusieurs semaines sur des versions internes, je peux donc attester de l’avancée des travaux en la matière. Evidemment, le langage lui-même de PowerShell reste en anglais, mais les messages d’information et d’erreur sont en français, ainsi que la documentation en ligne. Enfin, tout ça est en cours de traduction, car il n’est pas rare pour l’instant de voir des bouts de texte moitié en français moitié en anglais, avec quelques hiéroglyphes au milieu de tout ça.
     
    Pour l’instant PowerShell est disponible en français de deux façons : via MUI ou en langue dédiée. Sur mon poste, j’ai un multi-boot XP + Vista. J’ai installé les deux systèmes en anglais (en-US) puis j’ai installé le pack français par-dessus (la procédure est plus simple sous Vista que sous XP, mais au final le résultat est le même). Ma session Windows personnelle est donc en français. Ensuite, j’ai installé PowerShell en anglais + PowerShell MUI. Ce dernier a reconnu que ma session était en français et a donc configuré PowerShell pour utiliser le français. Si mon système était français « nativement », il aurait fallu que j’installe la version « dédiée » ou « native » de PowerShell en français. Mes tests ne portent donc que sur la version française fournie avec MUI. Je suppose que les deux packs seront disponibles au téléchargement, mais j’ignore s’ils le seront tous les deux dès la RC2.
     
    Le mystère demeure sur la disponibilité de la documentation supplémentaire en français. Cette documentation est une véritable mine d’informations, l’avoir en français serait donc un énorme plus pour l’adoption de Windows PowerShell par les utilisateurs francophones. A suivre de très près !
     
    Janel
    August 27

    Convertir les SID en noms et vice-versa

    Sur le forum microsoft.public.fr.scripting, Bouby6Killer demandait comment on pourrait traduire les SID visibles dans la branche HKEY_USERS afin d’obtenir les noms des comptes correspondants.
     
    Gilles Laurent a fourni une première solution en VBScript qui consiste à créer un dictionnaire SID -> Nom à partir des instances de la classe WMI Win32_AccountSID, et à résoudre chacun des SID apparaissant sous HKEY_USERS avec ce dictionnaire. Cette solution paraît très bonne mais elle souffre de performances très moyennes (apparemment variable selon le contexte) et surtout elle ne résout aucun SID de domaine si l’on n’est pas connecté au dit domaine. J’ai alors proposé d’utiliser l’utilitaire sid2name (disponible dans toutes les bonnes quincailleries sur Internet) et nous avons alors abouti à des solutions simples et performantes en langage Batch et en PowerShell (voir la discussion complète sur Google Groups). Je remets ici ces solutions pour référence :
     
    lookup.cmd (Gilles Laurent) 
    @for /f "delims=\ tokens=2" %%i in ('reg query hku ^| find "S-1" ^| find /v "Classes"') do @for /f "delims=," %%j in ('sid2name %%i') do @echo %%i,%%j
     
    lookup.ps1 (janel)
    reg query hku | % {if ($_ -match "S-.+\d$") {"{0},{1}" -f $matches[0], (sid2name $matches[0]).split(",")[0]}}
     
    Dans un cas comme dans l’autre, vous obtiendrez un résultat similaire à ceci :
     
    \> lookup
    S-1-5-19,NT AUTHORITY\LOCAL SERVICE
    S-1-5-20,NT AUTHORITY\NETWORK SERVICE
    S-1-5-21-1891254648-462032874-1584182948-41033,EUROPE\janel
    S-1-5-18,NT AUTHORITY\SYSTEM
     
    Bien. Mais mon esprit tatillon n’aimait que moyennement offrir une solution en PowerShell qui repose sur des commandes externes, sid2name et reg. Je sais, sid2name est un outil externe mais reg est une commande standard de XP, mais bon, disons que c’est une question de principe. Voici donc une version en PowerShell un peu plus longue mais totalement autonome :
     
    new-psdrive hku registry HKEY_USERS >$null
    $sids=@()
    dir hku: | % {if ($_ -match "S-.+\d$") {$sids+=$matches[0]}}
    $sids | foreach {
      $sid = new-object System.Security.Principal.SecurityIdentifier($_)
      $account = $sid.Translate([System.Security.Principal.NTAccount])
      "{0},{1}" -f $sid,$account.Value
    }
     
    On le voit, je m’affranchis de reg en utilisant les possibilités de PowerShell de monter les ruches de la base de registre en tant que lecteurs. Par défaut PowerShell monte les ruches HKCU et HKLM, il me faut donc commencer par monter la ruche HKU pour pouvoir l’utiliser.
     
    Ensuite, je remplace sid2name par des appels aux classes .NET qui vont bien. Les classes System.Security.Principal.SecurityIdentifier et System.Security.Principal.NTAccount décrivent respectivement un SID et un nom de compte. Ces deux classes possèdent une méthode Translate() qui permet de convertir un objet d’une classe à l’autre.
     
    Au passage, les performances de la version PowerShell autonome sont 8 à 10 fois meilleures que celles qui font appel à reg et à sid2name (que ce soit en Batch ou en PowerShell). Les temps de traitement restent dans tous les cas inférieurs à la seconde (du moins sur mon poste où j’ai testé tous ces scripts), mais si l’on voulait déployer un tel script sur une base de comptes importante on pourrait y voir là un avantage capital.
     
    Mais revenons à cette méthode Translate() que les deux classes exposent. Tiens donc… Du coup, on peut très facilement adapter sid2name et son comparse name2sid en deux scripts PowerShell :
     
    # convert-sid2name.ps1
    #
    # Convertit un SID en son nom de compte
    # Usage: convert-sid2name SID
    #
     
    trap {"SID non résolu."; continue}
     
    $sid = new-object System.Security.Principal.SecurityIdentifier($args[0])
    return ($sid.Translate([System.Security.Principal.NTAccount])).value
     
    # convert-name2sid.ps1
    #
    # Convertit un nom de compte en SID
    # Usage: convert-name2sid compte
    #
     
    trap {"Compte non résolu."; continue}
     
    $account = new-object System.Security.Principal.NTAccount($args[0])
    return ($account.Translate([System.Security.Principal.SecurityIdentifier])).value
     
    J’inclus systématiquement quelques lignes de commentaire en début de script pour pouvoir ensuite utiliser ma fonction whatis (billet du 25 juillet dernier) quand j’ai un doute sur l’usage d’un script.
     
    Vous remarquerez au passage l’usage du mot-clé trap pour capturer les erreurs éventuelles au moment de la conversion résultant d’un SID ou d’un nom de compte incorrects, ou simplement situés sur un domaine non joignable au moment de la requête (pas de panique : les noms de domaine déjà résolus sont quand même retrouvés, comme avec sid2name et name2sid). On pourrait sans doute étoffer cette gestion d’erreur en personnalisant le message selon le type d’erreur, à creuser si le cœur vous en dit.
     
    Janel
    August 26

    Windows PowerShell RC2 approche

    La date à laquelle la RC2 de Windows PowerShell sera disponible n’est pas encore fixée. En attendant, l’équipe de développement vient de publier un billet décrivant les principales modifications que la RC2 inclura:
     
     
    Dans un billet précédent j'avais déjà rapidement évoqué les nouvelles vues disponibles pour l’aide en ligne. A noter surtout l’évolution du support de WMI, un meilleur support des caractères spéciaux dans les noms de fichiers et de répertoires, et l’ajout des opérateurs -XOR et -BXOR. Je reviendrai sur ces changements dans les prochains jours.
     
    Janel
    August 23

    Gestionnaire de tâches sous Vista

    Je suis en train de tester une version pré-RC1 de Windows Vista. Ce doit être ma trentième version de test, peut-être plus, je n'ai pas compté mais cela fait plus de trois ans que j'ai commencé à tester cet OS, donc à raison d'au moins une réinstallation par mois (j'ai au moins tenu ce rythme ces deux dernières années), je ne dois pas être loin du compte. J'ai fait la plupart de mes tests sur le même ordinateur, mon portable de travail: un Dell Latitude D600 avec 1 Go de RAM (pour ceux qui ne connaissent pas le D600, le processeur est un Intel P4-M à 1.7Ghz, et le modèle que j'ai est équipé d'un disque dur de 40Go).
     
    Le fait d'avoir toujours eu la même machine en trois ans de tests me permet de suivre l'évolution des performances de Vista. J'ai partitionné mon disque en deux pour garder la possibilité de basculer sous Windows XP en cas de problème. Jusqu'à présent, à chaque fois que j'ai dû repasser sous XP j'ai été frappé par le contraste entre la réactivité de Vista et celle de XP. Le moindre double-clic, la moindre ouverture de fenêtre, le moindre parcours d'une arborescence, tout est deux ou trois plus lent sous Vista. Enfin, je devrais dire "était". Car pour la première fois, avec cette pré-RC1, je retrouve des sensations proches sinon égales à celles de Windows XP. C'est très prometteur, ça!
     
    Bon, sinon je voulais juste signaler quelque chose qui n'est pas nouveau avec cette pré-RC1 mais qui est vraiment très sympathique, c'est le Gestionnaire de tâches de Windows Vista. La première chose qui frappe, c'est le fait qu'il ne soit pas forcément en avant-plan: on peut choisir un affichage forcé en avant-plan ou non, et par défaut ce n'est pas le cas. Je trouvais plutôt pénible cette contrainte sous XP (et précédents) de devoir toujours choisir entre réduire le Gestionnaire de tâches ou bien l'avoir au-dessus de toutes mes autres fenêtres...
     
    Le premier affichage se limite aux processus de l'utilisateur en cours. La liste est donc (à priori) relativement courte. Si l'on sélectionne l'option "afficher les processus de tous les utilisateurs", on doit confirmer l'élévation de privilèges avec le fameux LUA, et on se retrouve alors avec la liste complète des processus exécutés par la machine. Cet affichage en deux temps n'est pas anodin: le premier affichage ne demande aucune confirmation, on peut donc contrôler ses propres processus sans requérir d'élévation de privilèges. Arrêter un processus qu'on a démarré reste donc une tâche simple et rapide. Par contre, si l'on veut commencer à fouiller les arcanes du système et éventuellement interrompre un processus qu'on n'a pas démarré soi-même, alors il faut basculer vers le deuxième affichage ("afficher les processus de tous les utilisateurs") et pour des raisons évidentes de sécurité il faut montrer patte blanche et confirmer l'élévation de privilèges.
     
    Dans l'onglet Processus, les colonnes "Description" et "Ligne de commande" ont fait leur apparition. La "Ligne de commande" est particulièrement utile pour identifier l'origine d'un processus douteux, ou pour déceler les paramètres passés au démarrage d'une application ou d'un service. Allez dans Affichage > Sélectionner les colonnes... pour afficher ces nouvelles colonnes.
     
    Un nouvel onglet Services est apparu. Cet onglet reprend la liste des services enregistrés par le système. La principale différence avec la console de gestion des services (services.msc) est l'affichage du PID du processus qui exécute chaque service. On peut basculer de l'onglet Processus à l'onglet Services et vice-versa en choisissant l'option correspondante dans le menu contextuel d'un processus ou d'un service. On peut donc maintenant facilement identifier et manipuler le processus qui exécute un service particulier, ou à l'inverse savoir quel service est exécuté par un processus en particulier. Evidemment, les administrateurs chevronnés resteront fidèles à la commande tasklist (ou à tout autre équivalent de leur choix), mais je crois que l'initiative est plutôt bonne pour tous les autres. Egalement, l'onglet Services offre un lien vers la console de gestion des services, en cas de besoin.
     
    Pour le reste, le Gestionnaire de tâches n'a pas fondamentalement changé. A noter, l'onglet Performances offre un lien vers le Moniteur de Ressources, très belle réalisation graphique qui ne demande qu'à mûrir dans ses fonctionnalités.
     
    Janel
    August 03

    Je gère mon PATH comme je veux

    Lorsque j’écris des scripts, ou que je teste des nouveaux utilitaires, j’aime bien travailler dans un répertoire temporaire à partir duquel je peux les exécuter (en n’oubliant de les préfixer de .\ pour indiquer le répertoire courant). Et puis, dans la même session PowerShell, je peux avoir besoin de me déplacer dans un autre répertoire, et dans ce cas-là je perds la capacité d’exécuter ces outils car la plupart du temps je n’ai pas pris la peine de mettre le répertoire en question dans ma variable d’environnement PATH. Et franchement, retaper le chemin complet à chaque fois peut être très vite très contraignant, surtout si ce chemin d’accès est de la forme c:\documents and settings\bibi\my documents\... .
     
    Pour cela (et pour d’autres usages éventuels), je me suis écrit ce petit script tout simple qui ajoute un répertoire au PATH.
     
    # add-path.ps1
    #
    # Ajoute un repertoire à la variable d’environnement PATH.
    #
    # Usage: add-path [-path] path [[-scope] {System | User}] [-save]
    #
    # - path est le chemin d’accès au repertoire à ajouter.
    #
    # - scope spécifie quel PATH vous voulez modifier (System ou User).
    #   Par défaut, c’est User qui est modifié.
    #
    # - save indique que la modification sera sauvegardé dans la base de registre.
    #   Si ce paramètre n’est pas spécifié, la modification n’affectera que la session en cours,
    #   et tout usage ultérieur d’add-path dans cette même session effacera cette modification.
    #
     
    param (
      [string]$path = (throw "Usage: add-path [-path] path [[-scope] {System | User}] [-save]"),
      [string]$scope = "User",
      [switch]$save
    )
     
    $AvailScopes = "System", "User"
     
    if ($AvailScopes -notcontains $scope) {
      throw "$scope n’est pas une valeur possible pour scope. Veuillez utiliser une de ces valeurs: $([string]::join(', ',$AvailScopes))."
      return
    }
     
    $wsh = new-object -com Wscript.Shell
    $AvailScopes | % {invoke-expression ('$'+$_+'Path = $wsh.Environment("'+$_+'").Item("Path")')}
    invoke-expression ('$'+$scope+'Path += ";$path"')
    $env:path = "$SystemPath;$UserPath"
     
    if ($save) {
      invoke-expression ('$wsh.Environment("'+$scope+'").Item("Path") = $'+$scope+'Path')
    }
     
    Quelques exemples d’utilisation :
     
    # ajouter le répertoire c:\temp au PATH pour la session en cours uniquement :
    PS> add-path c:\temp
     
    # ajouter le répertoire c:\localbin au PATH système et enregistrer la modification :
    PS> add-path c:\localbin system –save
     
    # ajouter le repertoire en cours au PATH pour la session en cours uniquement :
    PS> add-path (pwd).path
     
    L’intérêt principal de ce script (à mes yeux) est qu’il modifie le PATH pour la session en cours. Je n’ai donc pas besoin de basculer dans une nouvelle session pour profiter de la mise à jour. Et plus fort encore J, si je ne spécifie pas le paramètre –save, la modification n’affectera pas l’environnement de mes autres sessions (PowerShell ou toute autre application qui accède aux variables d’environnement). Cela me permet donc de travailler dans un environnement dynamique sans risque de provoquer des effets indésirables sur le reste de Windows.
     
    Maintenant, le contenu du script mérite sans doute quelques explications. J’avais fait une première version nettement moins absconse, et puis je me suis dit qu’avec ce script je pouvais tester certaines techniques d’écriture intéressantes. Ce qui m’intéressait au premier chef, c’était l’exécution d’expressions dans lesquelles le nom des variables serait construit dynamiquement. L’idée m’est venue avec ce script parce que je trouvais un peu bête d’avoir une première série d’instructions qui manipulent le PATH système puis une deuxième série d’instructions qui manipulent le PATH utilisateur. Les deux séries sont strictement identiques au détail près du nom de la variable. Voici un extrait de la première version de mon script :
     
    $sysreg = "hklm:\system\currentcontrolset\control\session manager\environment"
    $userreg = "hkcu:\environment"
    $syspath = (get-itemproperty $sysreg).path
    $userpath = (get-itemproperty $userreg).path
     
    switch ($scope) {
      "System" {
        $syspath += ";$path"
        $env:path = "$syspath;$userpath"
        if ($save) {
          set-itemproperty $sysreg -name Path -value $syspath
        }
    }
      "User" {
        $userpath += ";$path"
        $env:path = "$syspath;$userpath"
        if ($save) {
          set-itemproperty $userreg -name Path -value $userpath
        }
      }
      default {"$scope n’est pas une valeur possible pour scope. Veuillez spécifier System ou User."}
    }
     
    Le déroulement de cette version est sans doute plus clair. Vous noterez au passage qu’entre les deux versions j’ai changé la façon d’accéder à la variable PATH. Dans la première version (ci-dessus) j’accédais directement à la base de registre, alors que dans la deuxième version (plus haut) j’utilise les propriétés de l’objet COM WScript.Shell. Cela m’assure une meilleure compatibilité de mon script dans le cas (improbable, mais sait-on jamais) où Microsoft déciderait de changer le chemin d’accès à la variable. Cela me permet surtout d’éviter des fausses manips avec la base de registre – on a vite fait de glisser sur une coquille !
     
    Bien, ces digressions mises à part, qu’est-ce que j’ai donc voulu faire dans la deuxième version de mon script ? Très simplement, là où je devais écrire explicitement $UserPath += ";$path", à la place je compose une chaîne de caractères qui récupère au moment de l’exécution la partie texte correspondant à User dans $UserPath depuis une variable. La même séquence est exécutée avec System au lieu de User. Ensuite, je demande à PowerShell d’exécuter cette chaîne de caractères. La commandelette qui fait ce numéro de passe-passe s’appelle invoke-expression :
     
    PS> $expression = "get-process powershell"
    PS> invoke-expression $expression
     
    Handles  NPM(K)    PM(K)      WS(K) VM(M)   CPU(s)     Id ProcessName
    -------  ------    -----      ----- -----   ------     -- -----------
        869       7    21180        976   133     4,02   3180 powershell
        173       5    30272         24   136     1,26  19472 powershell
        439       6    32040      32136   149     8,70  22788 powershell
     
    Attention à un point important : si vous encadrez l’expression par des guillemets doubles (comme dans l’exemple ci-dessus), les variables contenues dans l’expression seront évaluées avant que l’expression soit passée à invoke-expression pour exécution. Si vous voulez préserver l’affichage des variables telles quelles, vous devrez encadrer l’expression par des guillemets simples :
     
    PS> $command = "get-process"
    PS> $parameter = "powershell"
    PS> $expression = "$command $parameter"
    PS> $expression
    get-process powershell
    PS> invoke-expression $expression
     
    Handles  NPM(K)    PM(K)      WS(K) VM(M)   CPU(s)     Id ProcessName
    -------  ------    -----      ----- -----   ------     -- -----------
        869       7    21180        976   133     4,02   3180 powershell
        173       5    30272         48   136     1,26  19472 powershell
        538       6    32528      32780   150     9,15  22788 powershell
     
    PS> $invoke = 'invoke-expression $expression'
    PS> $invoke
    invoke-expression $expression
    PS> invoke-expression $invoke
     
    Handles  NPM(K)    PM(K)      WS(K) VM(M)   CPU(s)     Id ProcessName
    -------  ------    -----      ----- -----   ------     -- -----------
        869       7    21180        976   133     4,02   3180 powershell
        173       5    30272         48   136     1,26  19472 powershell
        441       6    32900      33152   150     9,65  22788 powershell
     
    PS> $scope = "User"
    PS> $expression = '$'+$scope+'Path = "$scope"'
    PS> invoke-expression $expression
    PS> $UserPath
    User
     
    Voilà pour la partie un peu « originale » (n’ayons pas peur des mots J) de ce script.
     
    Un dernier point sur la manipulation de la variable d’environnement PATH : alors que je travaillais à mon script add-path, je suis retombé par hasard sur le script qu’Abhishek (un des membres de l’équipe PowerShell) avait posté sur son blog le 15 mai dernier. Ce script, edit-path, permet d’éditer le PATH système depuis le bloc-notes en présentant les répertoires ligne par ligne. On peut facilement adapter le script pour qu’il permette également d’éditer le PATH utilisateur. Avec add-path, voilà une vraie boîte à outils professionnelle pour gérer le PATH comme bon vous semble !
     
    Janel
    August 01

    Demandez la version de vos fichiers

    Vous est-il arrivé de vouloir vérifier la version d’une application ou d’une DLL ? Personnellement ça m’arrive souvent, notamment lorsque je teste des produits en version d’évaluation et que j’ai besoin de remonter un bogue en précisant la version exacte sur laquelle j’ai rencontré le problème. Ou alors, je peux être amené à devoir résoudre un conflit entre plusieurs DLL, et seul un examen attentif des versions me permettra de démêler l’embrouille.
     
    Le moyen le plus simple, et qui marche pour tout exécutable, consiste à :
    • Ouvrir l’Explorateur Windows,
    • Naviguer jusqu’au fichier en question,
    • Afficher ses propriétés,
    • Sélectionner l’onglet Version.
    Toutes les infos utiles sont là (normalement J).
     
    Mais si je veux récupérer ces infos depuis une session PowerShell, c’est (apparemment) un peu plus compliqué. Un get-childitem monapplication.exe ne retourne aucune information sur la version de mon application. En fait, ces infos sont accessibles par la méthode statique GetVersionInfo() de la classe .NET System.Diagnostics.FileVersionInfo :
     
    PS> $powershell = get-childitem $pshome\powershell.exe
    PS> [system.diagnostics.fileversioninfo]::getversioninfo($powershell)
     
    ProductVersion   FileVersion      FileName
    --------------   -----------      --------
    6.0.5429.0       6.0.5429.0 (w... C:\WINDOWS\system32\WindowsPowerShell\v1.0\powershell.exe
     
    PS> [system.diagnostics.fileversioninfo]::getversioninfo($powershell) | format-list *
     
    Comments           :
    CompanyName        : Microsoft Corporation
    FileBuildPart      : 5429
    FileDescription    : PowerShell.EXE
    FileMajorPart      : 6
    FileMinorPart      : 0
     
    Ce n’est pas que je sois fainéant, mais la saisie de ces lignes me fatigue rapidement, surtout quand je dois répéter l’opération souvent dans la même journée. Le plus simple ne serait-il pas d’avoir accès à ces informations directement depuis les propriétés d’un fichier ? Qu’à cela ne tienne, je peux très facilement personnaliser le type correspondant à un fichier. Au fait, c’est quoi ce type ?
     
    PS> (get-childitem $pshome\powershell.exe).gettype().fullname
    System.IO.FileInfo
     
    Ok, je vais donc créer un fichier FileInfo.types.ps1xml contenant la mise à jour souhaitée (le nom du fichier est arbitraire, seule l’extension .ps1xml est obligatoire) :
     
    <Types>
    <Type>
       <Name>System.IO.FileInfo</Name>
       <Members>
        <ScriptProperty>
         <Name>FileVersionInfo</Name>
         <GetScriptBlock>
          [System.Diagnostics.FileVersionInfo]::GetVersionInfo($this)
         </GetScriptBlock>
        </ScriptProperty>
       </Members>
    </Type>
    </Types>
     
    Ensuite, je charge cette mise à jour dans ma session PowerShell :
     
    PS> update-typedata fileinfo.types.ps1xml
     
    Je peux maintenant me servir des nouvelles propriétés des objets System.IO.FileInfo :
     
    PS> dir $pshome\powershell.exe | fl *
     
    PSPath            : Microsoft.PowerShell.Core\FileSystem::C:\WINDOWS\system32\WindowsPowerShell\v1.0\powershell.exe
    PSParentPath      : Microsoft.PowerShell.Core\FileSystem::C:\WINDOWS\system32\WindowsPowerShell\v1.0
    PSChildName       : powershell.exe
    PSDrive           : C
    PSProvider        : Microsoft.PowerShell.Core\FileSystem
    PSIsContainer     : False
    Mode              : -----
    FileVersionInfo   : File:             C:\WINDOWS\system32\WindowsPowerShell\v1.0\powershell.exe
                        InternalName:     POWERSHELL
                        OriginalFilename: PowerShell.EXE
                        FileVersion:      6.0.5429.0 (winmain(wmbla).060727-0105)
                        FileDescription:  PowerShell.EXE
                        Product:          Microsoft® Windows® Operating System
                        ProductVersion:   6.0.5429.0
                        Debug:            False
                        Patched:          False
                        PreRelease:       False
                        PrivateBuild:     True
                        SpecialBuild:     False
                        Language:         English (United States)
     
    Basename          : powershell
    Name              : powershell.exe
    Length            : 330240
    DirectoryName     : C:\WINDOWS\system32\WindowsPowerShell\v1.0
     
    PS> (dir $pshome\powershell.exe).fileversioninfo.language
    English (United States)
     
    PS> dir $pshome\*.dll,$pshome\*.exe | ft name,{$_.fileversioninfo.productversion}
     
    Name                                                        $_.fileversioninfo.productversion
    ----                                                        ---------------------------------
    pwrshmsg.dll                                                6.0.5429.0
    pwrshsip.dll                                                6.0.5429.0
    powershell.exe                                              6.0.5429.0
     
    Parfait. Je n’ai plus qu’à copier le fichier FileInfo.types.ps1xml dans mon répertoire $PSSTARTUP pour qu’il soit automatiquement pris en compte dans mes futures sessions. La variable globale $PSSTARTUP est définie ainsi dans mon profil :
     
    $global:PSSTARTUP = “$(split-path $profile)\scripts\startup”
     
    Comme je l’ai décrit dans un précédent billet, mon profil charge automatiquement les fichiers *.ps1 et *.types.ps1xml contenus dans ce répertoire. Et voilà !
     
    Janel