Jacques 的个人资料janel照片日志列表 工具 帮助
1月21日

Mes scripts PowerShell sont polyglottes, même en v1 !

Suite à la publication de mon billet sur la « localisation des scripts » dans PowerShell, une discussion intéressante a démarré sur un forum de PowerShell-Scripting.com où j’avais également publié l’information. Sur ce forum, Laurent Dardenne a évoqué les techniques possibles pour faire de la localisation avec la v1 de PowerShell. Je vous rappelle que la technique exposée dans mon billet repose sur des fonctionnalités apparues avec la v2.
 
Malheureusement, j’ai trouvé les techniques décrites par Laurent plutôt complexes et pas forcément tout à fait adaptées à ce que j’ai besoin de faire quand je veux proposer les différents messages d’un script en plusieurs langues. J’ai donc pris le temps de créer une fonction, très simple donc à priori très facilement perfectible, pour couvrir les besoins les plus courants sans avoir à installer la v2 de PowerShell.
 
Tout d’abord, voici le corps de la fonction (je l’ai appelée import-culturaldata pour la distinguer de la commandelette disponible dans la v2, mais je ne suis pas sûr que ce nom soit très approprié – je suis preneur de toute suggestion) :
 
function import-culturaldata
{
  param (
    [string]$filename = [IO.Path]::GetFileNameWithoutExtension((split-path -leaf $myinvocation.MyCommand)),
    [System.Globalization.CultureInfo]$UIculture = (get-UIculture)
  )
 
  if ($myInvocation.ScriptName) { $scriptname = $myInvocation.ScriptName }
  else { $scriptname = $myInvocation.MyCommand.Definition }
 
  $fullpath = $(split-path $ScriptName) + "\$UIculture\$filename" + ".psl1"
 
  $ht = @{}
  get-content $fullpath | where {($_.TrimStart()) -ne "" -and ($_.TrimStart())[0] -ne "#"} | foreach {
    $key, $value = $_.Split("=", 2)
    $ht.Add($key.Trim(), $value.TrimStart())
  }
  $ht
}
 
La fonction accepte deux paramètres :
 
- $filename – par défaut, le nom du script qui appelle la fonction (sans son extension)
- $UIculture – par défaut, la culture de la session en cours
 
La fichier lu doit avoir une extension .psl1 (pour PowerShell Localization, v1-compliant). J’ai tenu à me démarquer de l’extension choisie pour le même usage dans la v2 (.psd1) car le format d’un fichier .psd1 est potentiellement bien plus complexe que celui utilisé par ma fonction. Un fichier .psl1 doit contenir des entrées au format suivant :
 
<nom> = <valeur>
 
<nom> peut être n’importe quel texte pouvant être utilisé par PowerShell pour créer un nom de variable. Le caractère "=" est interdit.
<valeur> peut être n’importe quel texte, y compris contenant des "=". Je n’ai pas testé l’insertion de here-strings mais je doute que ça marche de manière totalement transparente. En revanche, on peut insérer des variables ({0}, {1}, …) qui pourront être substituées en cours de script avec l’opérateur -f. Je fournis une illustration de cet usage dans l’exemple ci-dessous.
 
Pour faciliter la lecture d’un fichier .psl1 on pourra insérer des lignes vierges, des espaces avant ou après <nom>, des espaces avant <valeur> et des lignes de commentaire commençant par "#" (précédé ou non par des espaces).
 
La fonction retourne un tableau associatif (hashtable en anglais) dont les paires clé/valeur sont créées à partir des entrées du fichier (clé = <nom>, valeur = <valeur>).
 
Prenons un exemple d’utilisation archi-simple. Pour cet exemple, imaginons que j’ai sauvegardé la fonction ci-dessus dans un fichier c:\outils\import-culturaldata.ps1. Pour tester la fonction, j’ai un répertoire c:\tests qui contient un fichier test-local.ps1 et deux sous-répertoires, c:\tests\fr-FR et c:\tests\en-US contenant chacun un fichier test-local.psl1.
 
Voyons d’abord le contenu des deux fichiers test-local.psl1 :
 
PS> get-content fr-FR\test-local.psl1   
# culture "fr-FR"  
  
Welcome = Bienvenue {0} au jeu "Devinez un jour" !  
Prompt  = Tapez un nom de jour  
Success = Vous avez deviné ! Toutes nos félicitations !  
PS>  
PS> get-content en-US\test-local.psl1  
# culture "en-US"  
  
     Welcome = Welcome {0} to the "Guess a day" game!  
     Prompt = Enter the name of any day  
     Success = You had it right! Congratulations!  
 
Vous aurez reconnu les messages déjà utilisés dans mon billet précédent (à peu de chose près).
 
Voyons maintenant le script de test, test-local.ps1 :
 
PS> get-content test-local.ps1
param ([System.Globalization.CultureInfo]$myculture = (get-UIculture))
 
. c:\outils\import-culturaldata.ps1
 
$messages = import-culturaldata -uiculture $myculture
 
$messages.Welcome –f $env:username
$messages.Prompt
$messages.Success
 
Vous le voyez, j’ai été au plus simple.
 
Le script accepte comme paramètre optionnel une culture, qui par défaut sera celle déjà en place. Ca permettra de tester rapidement les différentes localisations disponibles.
 
Ensuite, le script récupère les données localisées dans une variable $messages – vous noterez la syntaxe de ma fonction qui retourne son résultat dans une variable, au lieu de prendre le nom de la variable en argument comme c’est le cas avec import-localizeddata.
 
Pour finir, le script affiche les trois messages l’un à la suite de l’autre. Le premier message, $messages.Welcome, contient une variable {0} à laquelle je substitue le nom de l’utilisateur pour personnaliser le message.
 
Voici le script en action :
 
PS> .\test-local en-US
Welcome janel to the "Guess a day" game!
Enter the name of any day
You had it right! Congratulations!
PS>
PS> .\test-local
Bienvenue janel au jeu "Devinez un jour" !
Tapez un nom de jour
Vous avez deviné ! Toutes nos félicitations !
 
Voilà. Au passage, vous aurez peut-être trouvé un peu curieux le test suivant dans le code de la fonction :
 
  if ($myInvocation.ScriptName) { $scriptname = $myInvocation.ScriptName }
  else { $scriptname = $myInvocation.MyCommand.Definition }
 
J’ai dû ajouter ce test pour permettre à la fonction de tourner à la fois sur PowerShell v1 et la v2 CTP3. En effet, il semble qu’il y ait un bug avec la CTP3 dont $myinvocation.ScriptName ne retourne rien dans ce contexte, alors qu’elle devrait retourner le nom complet du script qui a appelé la fonction. Le bug est maintenant documenté, on peut donc espérer qu’il sera corrigé avec la prochaine fournée. En attendant, cette "rustine" permettra de maintenir le code tel quel sans se préoccuper de la version installée sur les postes qui l’utiliseront.
 
Bien à vous.
 
Janel
1月19日

PowerShell, svchost.exe et les fonctions avancées (1ere partie)

En cette fin de matinée mon PC semblait très occupé, au moins autant que moi. En jetant un œil à la liste des tâches en cours j’ai pu constater qu’il restait très peu de mémoire disponible. Après avoir fermé quelques applications inutilement restées ouvertes depuis vendredi dernier, j’ai vu qu’il restait encore plusieurs processus gourmands, et notamment deux svchost.exe consommant chacun plus de 50 Mo.

 

On sait que Windows utilise les processus svchost.exe pour héberger des services n’ayant pas leur propre exécutable. Chaque instance de svchost.exe peut héberger plusieurs services, parfois plusieurs dizaines. On sait moins comment on peut obtenir la liste des services hébergés par un processus svchost.exe donné. Il existe plusieurs méthodes, je m’attarderai ici sur l’une d’entre elles qui fait appel à Windows PowerShell.

 

Lorsqu’il s’agit de manipuler des processus et des services dans PowerShell, on pense immédiatement à deux commandelettes fournies en standard :

 

PS> get-process

                                        

Handles  NPM(K)    PM(K)      WS(K) VM(M)   CPU(s)     Id ProcessName

-------  ------    -----      ----- -----   ------     -- -----------

     27       1      380        376    11            1952 AEADISRV

     38       2      736        432    21            1968 agrsmsvc 

    183       3     2608        580    41            1024 Ati2evxx

[...]                                                                                                                       

                                                                                                                  

PS> get-service

 

Status   Name               DisplayName

------   ----               -----------

Stopped  AddFiltr           AddFiltr

Stopped  Adobe LM Service   Adobe LM Service

Running  AeLookupSvc        Application Experience

[...]                                                         

 

Comme on pourrait s’y attendre, on peut facilement obtenir la liste des processus nommés svchost.exe :

 

PS> get-process svchost

 

Handles  NPM(K)    PM(K)      WS(K) VM(M)   CPU(s)     Id ProcessName

-------  ------    -----      ----- -----   ------     -- ----------- 

    331       5     4020       3856    45             816 svchost

    732      11     6440       4872    56             884 svchost

    469      20   106252      13912   206             932 svchost

[...]

 

On peut même très facilement isoler les processus dont la mémoire de travail est supérieure à 50 Mo :

 

PS> get-process svchost | where {$_.workingset -gt 50MB}

 

Handles  NPM(K)    PM(K)      WS(K) VM(M)   CPU(s)     Id ProcessName

-------  ------    -----      ----- -----   ------     -- -----------

   1026      22    94800      79816   198            1068 svchost 

   2242     290   134076      98224   323            1080 svchost

 

 

Mais get-process ne fournit pas de méthode pour aller voir quels services se cachent dans ces processus. On pourrait à la rigueur s’en faire une idée en parcourant les modules chargés par chacun de ces processus, mais la liste serait assez rébarbative à parcourir et à recouper avec de véritables services Windows.

 

On aurait alors envie de se tourner vers get-service. Pendant un instant j’ai imaginé que les objets retournés par get-service avaient un numéro de tâche qui permettrait de les rattacher à un processus. Mais non. Ou alors, je n’ai pas trouvé.

 

J’ai alors pensé à un troisième larron : la classe WMI Win32_Service. PowerShell permet d’accéder très facilement aux classes WMI :

 

PS> get-wmiobject win32_service | get-member -membertype properties

 

 

   TypeName: System.Management.ManagementObject#root\cimv2\Win32_Service 

 

Name                    MemberType   Definition 

----                    ----------   ---------- 

AcceptPause             Property     System.Boolean AcceptPause {get;set;} 

AcceptStop              Property     System.Boolean AcceptStop {get;set;}

Caption                 Property     System.String Caption {get;set;}

CheckPoint              Property     System.UInt32 CheckPoint {get;set;} 

CreationClassName       Property     System.String CreationClassName {get;set;} 

Description             Property     System.String Description {get;set;}

DesktopInteract         Property     System.Boolean DesktopInteract {get;set;}

DisplayName             Property     System.String DisplayName {get;set;}

ErrorControl            Property     System.String ErrorControl {get;set;}

ExitCode                Property     System.UInt32 ExitCode {get;set;} 

InstallDate             Property     System.String InstallDate {get;set;}

Name                    Property     System.String Name {get;set;} 

PathName                Property     System.String PathName {get;set;} 

ProcessId               Property     System.UInt32 ProcessId {get;set;} 

ServiceSpecificExitCode Property     System.UInt32 ServiceSpecificExitCode {get;set;}

ServiceType             Property     System.String ServiceType {get;set;}

Started                 Property     System.Boolean Started {get;set;}

StartMode               Property     System.String StartMode {get;set;}

StartName               Property     System.String StartName {get;set;}

State                   Property     System.String State {get;set;}

Status                  Property     System.String Status {get;set;}

SystemCreationClassName Property     System.String SystemCreationClassName {get;set;}

SystemName              Property     System.String SystemName {get;set;} 

TagId                   Property     System.UInt32 TagId {get;set;}

WaitHint                Property     System.UInt32 WaitHint {get;set;}

[...]

 

Bingo ! La propriété ProcessId (surlignée ci-dessus) semble tout à fait correspondre à ce que je cherche, à savoir faire une jointure entre la liste des processus svchost.exe et la liste des services. Essayons :

 

PS> get-process svchost | where {$_.workingset -gt 50MB} | foreach {

>> $id = $_.id; gwmi win32_service | where {$_.processi d -eq $id}} |

>> ft proc*,name,displayname,stat* -auto

>>            

                                                                                                                        

ProcessId name                 displayname                     state   status

--------- ----                 -----------                     -----   ------

     1068 AudioEndpointBuilder Windows Audio Endpoint Builder  Running OK

     1068 CscService           Offline Files                   Running OK

     1068 EMDMgmt              ReadyBoost                      Running OK

[...]

 

Et voilà.

 

NB. Je suis obligé de tronquer les commandes sur plusieurs lignes pour que l'affichage soit lisible dans mon blog. En réalité, je tape les commandes les unes à la suite des autres sur un même ligne.

 

Pour celles et ceux qui s’interrogent: oui, on pouvait s’épargner la création d’une variable intermédiaire ($id) mais ça n’apportait aucune amélioration dans les performances de la requête, ça rendait la ligne un peu plus complexe à déchiffrer, et au final ça m’aurait éloigné de l’objet de cette discussion. J

 

Quant à mon problème de mémoire vive utilisée par svchost.exe, cet exercice m’a surtout permis de constater qu’il y avait beaucoup de monde pour utiliser cette mémoire, ce qui n’est pas forcément un problème en soi. Il y aurait eu deux ou trois services hébergés par chacun de ces processus, le problème aurait été autrement plus criant. Là, je me suis contenté de parcourir la liste et d’arrêter les services qui ne me paraissaient pas indispensables.

 

Il ne me restait plus qu’à pousser cette ligne de commandes dans un script, de manière à m’éviter de devoir rechercher l’information à chaque fois qu’il m’arrive de vouloir obtenir l’information. Dans sa version la plus simple, le fichier ressemblait alors à ceci :

 

PS> get-content get-hostedservice.ps1

# get-hostedservice

#                  

# Returns a list of services hosted by a process.

# Syntax: get-hostedservice -id ProcessId 

                        

param ([int]$Id)

                                                           

get-wmiobject Win32_Service | where {$_.ProcessId -eq $Id}    

 

Et voici un exemple de mise en oeuvre :

 

PS> gps svchost | where {$_.workingset -gt 50MB} | foreach {get-hostedservice $_.id} |

>> ft proc*,name,displayname,stat* -auto    

>>

 

ProcessId name                 displayname                     State   Status

--------- ----                 -----------                     -----   ------

     1068 AudioEndpointBuilder Windows Audio Endpoint Builder  Running OK

     1068 CscService           Offline Files                   Running OK

     1068 EMDMgmt              ReadyBoost                      Running OK

[...]

 

La ligne à taper est plus simple que la précédente, mais pas tant que ça. Encore faut-il se rappeler que le script accepte l’Id d’un processus comme paramètre. Et j’aurais aimé pouvoir simplement récupérer les objets du pipeline dans mon script, sans avoir à les traiter un par un dans ma ligne de commandes avec une boucle foreach.

 

C’est là que la v2 de PowerShell apporte quelques grosses améliorations qui vont nous simplifier encore plus la tâche. Mais nous verrons tout cela dans la 2e partie, à venir dans quelques jours. D’ici là, soyez sages ! J

 

Janel

1月16日

Mes scripts PowerShell sont polyglottes

Avec la v2 CTP3 de PowerShell arrivent une foultitude de nouvelles fonctionnalités, toutes plus sympathiques les unes que les autres. On parle beaucoup de la gestion de sessions à distance et de l’exécution asynchrone de tâches (la seconde dépendant de la première). Personnellement, une autre fonctionnalité a attiré mon attention ces jours-ci et m’a paru digne d’intérêt pour toute personne qui aura besoin de diffuser son script à des populations ne comprenant pas forcément toutes la même langue.
 
Il s’agit de la possibilité de personnaliser un script de manière à ce que les messages qu’il affiche soient automatiquement choisis en fonction de la langue de l’utilisateur. En fait, on peut même adapter d’autres variables que les messages, mais dans la pratique ce sont essentiellement les messages destinés à l’utilisateur qu’on voudra « localiser » (anglicisme fréquemment utilisé pour décrire les différents travaux d’adaptation d’un logiciel aux particularités linguistiques et autres d’un pays). Cette possibilité, dans PowerShell, s’appelle « script internationalization ». Vous pourrez en trouver une description relativement sommaire dans les « release notes » de la v2 CTP3.
 
La fonctionnalité de « script internationalization » repose sur une autre fonctionnalité, la gestion de « DATA sections ». Il s’agit de blocs de données isolés du code pour faciliter la lecture du script et sa maintenance éventuelle. Un bloc de donnée est déclaré avec le mot-clé spécial DATA, suivi dans sa forme la plus simple d’un nom de variable (sans le signe $) et du contenu de cette variable. La syntaxe complète et plus d’informations sont accessibles depuis PowerShell en tapant :
 
help about_data_sections
 
Mais passons directement à l’utilisation concrète de blocs de données dans un script que nous voudrons offrir en plusieurs langues.
 
Soit le script suivant, qu’on appellera GuessADay.ps1 :
 
# culture="fr-FR" par défaut
 
Data day {
     ConvertFrom-StringData @'
          d1 = lundi
          d2 = mardi
          d3 = mercredi
          d4 = jeudi
          d5 = vendredi
          d6 = samedi
          d7 = dimanche
'@
}
 
Data messages {
     ConvertFrom-StringData @'
          Welcome = Bienvenue au jeu "Devinez un jour" !
          Prompt = Tapez un nom de jour
          Success = Vous avez deviné ! Toutes nos félicitations !
'@
}
 
Data errors {
     ConvertFrom-StringData @"
          NotExist = {0} n'existe pas. La bonne réponse était {1}.
          WrongDay = Erreur! La bonne réponse était {0}.
"@
}
 
# importer les données traduites :
 
Import-LocalizedData -bindingvariable day -filename day -ea silentlycontinue
Import-LocalizedData -bindingvariable messages -filename messages -ea silentlycontinue
Import-LocalizedData -bindingvariable errors -filename errors -ea silentlycontinue
 
# Construire un tableau avec les noms de jours :
 
$days = $day.d1, $day.d2, $day.d3, $day.d4, $day.d5, $day.d6, $day.d7
 
# Choisir un jour au hasard :
 
$computerday = get-random $days
 
# accueillir le joueur et lui proposer de saisir un nom de jour :
 
$messages.Welcome
$userday = read-host $messages.Prompt
 
# vérifier le nom saisi et afficher un message en conséquence :
 
switch ($userday)
{
     $computerday { $messages.Success; break }
     {$days -notcontains $_} { $errors.NotExist -f $userday,$computerday; break }
     default { $errors.WrongDay -f $computerday }
}
 
Il s’agit d’un petit jeu extrêmement simple et totalement stupide. L’ordinateur choisit un jour de la semaine au hasard et demande à l’utilisateur de le deviner.
 
L’intérêt de ce script est de montrer comment on peut charger des messages adaptés à la langue de l’utilisateur, telle qu’elle est définie dans la session PowerShell en cours.
 
En l’occurrence, les messages du script GuessADay.ps1 ont été regroupés dans trois variables :
 
Data day {
     ConvertFrom-StringData @'
          d1 = lundi
          d2 = mardi
          d3 = mercredi
          d4 = jeudi
          d5 = vendredi
          d6 = samedi
          d7 = dimanche
'@
}
 
La première variable, $day, est un tableau contenant les noms des jours de la semaine. $day.d1 = "lundi", $day.d2 = "mardi", etc. Vous noterez la syntaxe du bloc DATA, un peu particulière. Je vous rappelle que vous trouverez plus d’explications dans l’aide en ligne (cf. plus haut dans ce billet).
 
Data messages {
     ConvertFrom-StringData @'
          Welcome = Bienvenue au jeu "Devinez un jour" !
          Prompt = Tapez un nom de jour
          Success = Vous avez deviné ! Toutes nos félicitations !
'@
}
 
La deuxième variable, $messages, contient les messages utilisés au cours du jeu. $messages.Welcome est un message de bienvenue, $messages.Prompt est le message utilisé pour inviter le joueur à saisir un nom de jour, et $messages.Success est le message affiché en cas de succès.
 
Data errors {
     ConvertFrom-StringData @'
          NotExist = {0} n'existe pas. La bonne réponse était {1}.
          WrongDay = Erreur! La bonne réponse était {0}.
'@
}
 
Enfin, la troisième variable, $errors, contient les messages d’erreur. Je les ai isolés des messages standards, notamment pour mettre en évidence une technique particulière qui est souvent utile pour les messages d’erreurs, à savoir l’inclusion de paramètres dans le message. Ici, $errors.NotExist est utilisé si le jour saisi ne correspond à aucun nom de jour connu, et $errors.WrongDay est utilisé si le jour saisi n’est pas celui que l’ordinateur avait choisi.
 
L’inclusion de paramètres utilise la technique de formatage de chaînes de caractères avec l’opérateur -f. Chaque paramètre est identifié dans le texte par son numéro d’ordre inséré entre crochets : {0} pour le premier paramètre, {1} pour le deuxième, et ainsi de suite. Au moment de l’affichage du message, il suffit de passer les paramètres à la suite du message et de l’opérateur -f :
 
$errors.NotExist -f $userday,$computerday
 
La ligne ci-dessus va afficher le message $errors.NotExist, en remplaçant {0} par $userday et {1} par $computerday. Et voilà. J
 
Tout cela est bien joli, mais tous les message de ce script sont en français. Alors, où est la traduction automatique tant vantée ?
 
Patientez, j’y viens ! En fait, il va nous falloir créer quelques fichiers supplémentaires, et même un (ou plusieurs) répertoire(s) supplémentaire(s). Prenons un exemple simple où nous voulons diffuser notre script à des américains. S’ils l’exécutent tel quel, ils risquent fort de ne pas comprendre grand-chose. On va donc créer des fichiers qui contiendront des traductions en anglais des différents messages du jeu.
 
Pour cela, on va d’abord créer un répertoire \en-US dans le répertoire où le script est stocké. Pourquoi « en-US » ? Parce que c’est le nom de la culture (l’ensemble des paramètres régionaux) retournée par un poste configuré pour un américain. On peut voir le nom de la culture utilisée pour l’interface de l’utilisateur en cours en tapant la commandelette suivante :
 
PS> get-UICulture
 
LCID             Name             DisplayName
----             ----             -----------
1036             fr-FR            Français (France)
 
L’exemple ci-dessus correspond à la configuration classique d’un utilisateur français. Chez un américain, la même commandelette devrait retourner un résultat différent :
 
PS> get-UICulture
 
LCID             Name             DisplayName
----             ----             -----------
1033             en-US            English (United States)
 
PowerShell va se servir du nom de la culture (“fr-FR” pour un français, "en-US") pour déterminer dans quel répertoire aller chercher les données spécifiques à cette culture. Cette opération est réalisée par la commandelette import-localizeddata :
 
Import-LocalizedData -bindingvariable day -filename day -ea silentlycontinue
Import-LocalizedData -bindingvariable messages -filename messages -ea silentlycontinue
Import-LocalizedData -bindingvariable errors -filename errors -ea silentlycontinue
 
Ici, le script réalise trois opérations d’importation distinctes, une par variable ($day, $messages et $errors). Chaque variable est stockée dans un fichier différent dont le nom est précisé après le paramètre –filename. L’extension du fichier n’est pas précisée, mais il s’agira forcément d’un fichier utilisant l’extension .psd1. On aura donc trois fichiers dans le répertoire \en-US :
 
PS> dir en-US
 
 
    Directory: C:\Users\janel\documents\tests\GuessADay\en-US
 
 
Mode                LastWriteTime     Length Name
----                -------------     ------ ----
-a---        16/01/2009     16:44        184 day.psd1
-a---        16/01/2009     17:13        176 errors.psd1
-a---        16/01/2009     16:49        193 messages.psd1
 
Si le script ne contenait qu’un seul bloc de données, on aurait eu un seul fichier de traduction à gérer. Dans ce cas, on pouvait ne pas préciser le nom de fichier et il suffisait de stocker les messages traduits dans un fichier .psd1 reprenant le nom du script (guessaday.psd1).
 
Mais voyons maintenant à quoi ressemblent les fichiers contenant les versions américaines de nos messages :
 
PS> dir en-US | foreach {"`n$_ :`n"; get-content $_.pspath}
 
 
day.psd1 :
 
 
# culture="en-US"
 
ConvertFrom-StringData @'
     d1 = Monday
     d2 = Tuesday
     d3 = Wednesday
     d4 = Thursday
     d5 = Friday
     d6 = Saturday
     d7 = Sunday
'@
 
 
errors.psd1 :
 
 
# Culture="en-US"
 
ConvertFrom-StringData @'
     NotExist = There is no such day as {0}. The correct answer was {1}.
     WrongDay = Wrong! The correct answer was {0}.
'@
 
 
messages.psd1 :
 
 
# Culture "en-US"
 
ConvertFrom-StringData @'
     Welcome = Welcome to the "Guess a day" game!
     Prompt = Enter the name of any day
     Success = You had it right! Congratulations!
'@
 
Dans chaque fichier on retrouve la même structure que dans le bloc de données correspondant, à ceci près qu’on ne reprend pas la déclaration "Data …". En fait, le fichier toute entier est lui-même considéré comme un bloc de données, et c’est l’opération d’importation qui s’occupe de faire le lien avec la variable correspondante.
 
Notez que la ligne de commentaire précisant la culture du fichier est tout à fait facultative. Je l’ai juste mise en tête de chaque fichier à titre d’information.
 
Une fois que tout ça est en place, il ne reste plus qu’à diffuser le script et ses fichiers de traduction (en faisant attention de toujours respecter l’arborescence avec les noms de culture).
 
Sur un poste français, voici ce que pourra donner une phase de jeu :
 
PS> .\guessaday
Bienvenue au jeu "Devinez un jour" !
Tapez un nom de jour: lundi
Erreur! La bonne réponse était dimanche.
 
Et le même script, sur un poste américain :
 
PS> .\guessaday
Welcome to the "Guess a day" game!
Enter the name of any day: Tuesday
You had it right! Congratulations!
 
Voilà. A vous de trouver un usage un tantinet plus utile. Evidemment, vous pouvez ajouter autant de répertoires que vous aurez de traductions à offrir, en faisant toujours attention de bien nommer les répertoires du nom de la culture à laquelle ils correspondent. Et n’oubliez pas que les messages utilisés dans le script seront ceux utilisés par défaut si aucun répertoire n’existe pour la langue de l’utilisateur. Dans mon exemple j’ai utilisé le français par défaut, mais il est possible que vous ayez à utiliser l’anglais par défaut et à fournir le français en traduction dans un répertoire fr-FR.
 
A vous de jouer !
 
Janel 
1月1日

Dix lignes de Bling?

En ce tout premier jour de l’année 2009, un nouveau script PowerShell fort intéressant a été posté par Clint Huffman sur Codeplex :
 
 
Bling.ps1 permet de créer un graphique à partir d’un log créé par Perfmon, l’outil de suivi de performances de Windows. Les logs sont acceptés au format BLG ou CSV. Chaque compteur de performance présent dans le log est représenté par une ligne sur le graphique.
 
La page ci-dessus fournit toutes les indications pour installer et utiliser le script. Je n’ai pas encore eu le temps de le tester, mais je ne manquerai pas de vous faire part de mes remarques le cas échéant. N’hésitez pas à laisser les vôtres en commentaire !
 
Même si dans sa v1 le script a quelques limitations, il s’agit à l’évidence d’une belle réalisation qui pourra inspirer de nombreux scripteurs.
 
Profitez bien, et comme dirait l’autre : allez 2009 !
 
Janel