| Jacques's profilejanelPhotosBlogLists | Help |
|
June 26 Mise à jour du BIOS pour les portables HP Compaq nc8430Possesseur d’un portable HP Compaq nc8430 sur lequel tourne Windows Vista Ultimate Edition 32 bits (US-English), j’ai été témoin il y a quelques jours d’un bel écran bleu comme je n’en avais encore jamais vu sur ce système depuis les premières versions beta. Voici la trace du crash dans le journal système (évènement 1001 dont la source est BugCheck) :
The computer has rebooted from a bugcheck. The bugcheck was: 0x00000101 (0x00000061, 0x00000000, 0x831cb120, 0x00000001). A dump was saved in: C:\Windows\MEMORY.DMP.
Au redémarrage, Windows m’a indiqué que le problème était corrigé par une version plus récente du BIOS de ma machine, disponible sur le site de HP. J’ai immédiatement installé cette mise à jour, notée F.10 et datée du 17 avril 2007.
Si vous avez une configuration matérielle & logicielle identique à la mienne, et que votre portable utilise une version antérieure du BIOS, n’hésitez pas à faire la mise à jour, elle vous évitera peut-être la même mésaventure !
Pour vérifier la version de votre BIOS :
Avec PowerShell –
PS> gwmi win32_bios
SMBIOSBIOSVersion : 68YVD Ver. F.10
Manufacturer : Hewlett-Packard
Name : KBC Version 40.17
SerialNumber : HUB7020881
Version : HPQOEM – 1
Avec l’interpréteur de commandes standard –
C:\> wmic bios get smbiosbiosversion
SMBIOSBIOSVersion
68YVD Ver. F.10
Pour télécharger la mise à jour depuis le site de HP :
Le premier lien proposé en téléchargement – HPQFlash … Microsoft Windows/Vista-Based – correspond à l’installation qui est lançable directement depuis Windows. Il faudra prévoir un redémarrage pour que la mise à jour soit prise en compte.
A bon entendeur,
Janel De l'usage du pipeline pour optimiser les performances de PowerShellSur une liste de discussion dédiée à PowerShell, quelqu’un demandait comment récupérer rapidement le dernier évènement 6009 du journal système. Cet évènement correspond au redémarrage du système.
L’utilisateur ne voulait que le dernier évènement, pas la palanquée de redémarrages consignés dans le journal système. Son « one-liner » , bien que relativement concis, mettait plus d’une minute et demie avant de lui retourner l’information souhaitée:
@(get-eventlog system | where-object {$_.eventid -eq 6009})[0] | format-table TimeWritten, EventID
En effet, la suite de commandes parcourt l’intégralité du journal système, filtre tous les évènements dont l’ID est 6009, stocke le résultat dans un tableau et en extrait le premier élément qui, pour finir, est présenté en format table avec les deux propriétés TimeWritten et EventID. C’est donc beaucoup de travail pour une seule information souhaitée.
Une première réponse a réduit le temps de parcours à environ 20 secondes :
foreach ($e in (get-eventlog system)) {if ($e.EventID -eq 6009) {return $e.TimeWritten }}
L’ingéniosité apparente de cette solution est qu’elle interrompt le processus dès le premier évènement 6009 rencontré. Mais on était encore loin de l’optimisation maximale possible, qui fut proposée par Jim Truher :
get-eventlog system | where {$_.eventid -eq 6009} | foreach {$_|ft timewritten,eventid; exit}
Le “one-liner” ci-dessus mit moins d’une seconde pour retourner l’information sur le poste de l’utilisateur. La stratégie était apparemment la même que la précédente: lire les évènements du journal système, filtrer les évènements 6009, et pour chacun de ces évènements, l’afficher et quitter pour éviter de devoir parcourir les suivants. Pourquoi alors une telle différence entre 20 secondes et moins d’une seconde ?
La structure utilisée dans la première réponse est de la forme :
foreach ($e in (get-eventlog system))
{
…
}
Avec cette structure, PowerShell parcourt l’ensemble du journal système *puis* passe au bloc de commandes entre accolades. A l’inverse, la structure utilisée par Jim utilise pleinement la capacité de « streaming » du pipeline de PowerShell. La commandelette get-eventlog émet les objets l’un après l’autre vers le pipeline, et c’est au fur et à mesure qu’ils sont émis que le pipeline les transmet à la commande suivante, et ainsi de suite.
Au final, la partie foreach {$_ | ft timewritten,eventid; exit} reçoit très rapidement son premier évènement 6009. Et l’instruction exit termine immédiatement la procédure.
A propos de l’instruction exit, si on la saisit dans une ligne de commande interactive, elle met fin à la session dans laquelle on était. Pas cool ! D’où la recommandation de Jim de coller ce « one-liner » dans un script, ce qui limite le rôle d’exit à terminer le script.
Pour éviter une telle contrainte, il suffit de remplacer exit par break :
get-eventlog system | where {$_.eventid -eq 6009} | foreach {$_|ft timewritten,eventid; break}
A vous de jouer !
Janel June 15 Quelques subtilités des tableaux avec PowerShellEn répondant à la question d’un utilisateur sur le forum MS américain dédié à PowerShell, j’ai découvert quelques subtilités de la classe System.Array utilisée par PowerShell pour manipuler des tableaux. Dans PowerShell, on créera un tableau le plus souvent en assignant des valeurs particulières à une variable :
PS> $m = (5,10,15) # on aurait aussi pu écrire $m = 5,10,15
PS> $m
5
10
15
On peut également créer un tableau à plusieurs dimensions :
PS> $n = (4,8,12),(5,10,15)
PS> $n[0]
4
8
12
PS> $n[1][2]
15
Vous remarquerez la syntaxe utilisée pour accéder à un élément particulier d’un tableau à plusieurs dimensions : $n[x][y]. Pourquoi n’écrit-on pas $n[x,y] ? Essayons :
PS> $n[1,2]
5
10
15
20
Kézako ? En réalité, PowerShell interprète cela comme : donne-moi les valeurs de $n entre la valeur d’indice 1 et la valeur d’indice 2. Ici, la valeur d’indice 1 correspond à la 2e ligne du tableau – soit les valeurs 5, 10, 15 et 20 – et comme il n’existe pas de 3e ligne dans le tableau, la valeur d’indice 2 est vide.
Cette syntaxe surprenante de prime abord permet sans doute des opérations très rapides sur les matrices. Mais passons à autre chose.
La question de départ sur le forum portait sur la possibilité avec PowerShell d’initialiser un tableau avec des dimensions données sans pour autant lui passer des valeurs particulières. Un coup d’œil à la classe System.Array m’a permis de voir qu’il existe une méthode CreateInstance() qui répond à la question :
PS> $a = [System.Array]::CreateInstance([int],2,2)
PS> $a[1,1] = 5
PS> $a
0
0
0
5
Quoi ? Maintenant, la syntaxe $a[1,1] marche pour assigner une valeur à un élément distinct du tableau ? Hé oui. Ne me demandez pas pourquoi, même si j’en ai une vague idée, je ne saurai pas l’exprimer ici. Un début de réponse est fourni dans le nom du type enregistré pour chacun des deux tableaux $a et $n :
PS> $a.GetType()
IsPublic IsSerial Name BaseType
-------- -------- ---- --------
True True Int32[,] System.Array
PS> $n.GetType()
IsPublic IsSerial Name BaseType
-------- -------- ---- --------
True True Object[] System.Array
Notez le nom du type de $a : Int32[,] – la virgule donne une indication majeure sur la façon dont on accédera aux différentes dimensions du tableau.
Mais passons encore à autre chose… En fouillant un peu plus les différentes constructions possibles de la méthode CreateInstance(), j’ai découvert qu’on pouvait forcer le démarrage d’un index à une valeur différente de zéro.
Je vous rappelle – ou je vous apprendrai si vous venez de vous réveiller d’un long sommeil hypnotique – que chaque dimension commence à zéro : la première ligne d’un tableau $n à deux dimensions est $n[0], la deuxième ligne est $n[1], et ainsi de suite. Or, CreateInstance() permet de fixer une autre valeur de départ, et ce pour chaque dimension indépendamment ! C’est le genre de fonctionnalité à manier avec précaution si l’on ne veut pas se mélanger les pinceaux. Voici un exemple simple qui fait commencer à 1 les indices d’un tableau à deux dimensions :
PS> $b = [Array]::CreateInstance([int], (2,2), (1,1))
PS> $b
0
0
0
0
PS> $b[1,1] = 5
PS> $b
5
0
0
0
Voilà qui peut permettre de simplifier des procédures où l’on devait systématiquement tenir compte du décalage entre la notion de « 1er » élément et le fait que son indice était « 0 ». Voire d’autres usages ésotériques ?...
J’espère en tout cas vous avoir donné envie d’en savoir plus.
Amusez-vous bien !
Janel June 11 Identifier rapidement les alias non standardsAu cours d’une session PowerShell, vous avez de nombreux alias à votre disposition : ceux créés par défaut dans PowerShell, ceux que vous avez ajoutés à votre profil, et éventuellement ceux que vous avez définis en cours de session pour accélérer certaines tâches spécifiques.
Et puis, arrive le moment où vous écrivez un script que vous allez devoir partager, et vous voulez vous assurer que ceux qui recevront votre script pourront l’exécuter sans problème. A priori, la solution la plus simple consisterait à bannir l’usage de tout alias dans un script, ce qui vous assure une portabilité maximale (au problème près de l’utilisation de commandelettes non standards, il faudra également que j’écrive un petit script pour les identifier).
Mais si vous voulez quand même garder l’usage de certains alias qui vous paraissent beaucoup plus naturels que les noms complets de leurs commandelettes – je pense par exemple à dir, popd/pushd, cd, etc – voici une petite fonction à intégrer à votre profil qui vous permettra de rapidement identifier si un alias est standard ou non :
function test-customalias ($alias) {
$stdalias = powershell -noprofile -command {get-alias | % {$_.name}}
[bool](get-alias $alias | ? {$stdalias -notcontains $_})
}
PS> test-customalias tee
False
PS> test-customalias new
True
Dans l’exemple ci-dessus, tee est un alias standard et donc la fonction test-customalias retourne False. A l’inverse, new est un alias ajouté par mon profil, la fonction retourne donc True.
S’il vous paraîtrait plus logique que la fonction retourne True si un alias est standard et False s’il ne l’est pas, je vous laisse renommer la fonction (par exemple en test-standardalias) et remplacer -notcontains par -contains.
Pour finir, il ne vous reste plus qu’à ajouter un alias pour cette fonction !
Janel Ne garder que la plus vieille instance d'une série de processDans son dernier billet (daté du mois de mai, mais je récupère mon retard comme je peux), Arul Kumaravel proposait une solution pour terminer toutes les instances d’une application sauf celle qui a commencé en premier :
Je sais, un billet – et en particulier de la part d’un employé Microsoft sur son blog MSDN – a une vocation pédagogique non négligeable. Moyennant quoi (comme dirait Guy Forget) il faut dans la mesure du possible écrire des scripts facilement lisibles : utiliser les noms complets des commandelettes au lieu de leurs alias, recourir à des lignes d’instructions plutôt courtes et donc éviter l’enchaînement de commandes imbriquées d’un pipeline à l’autre, etc.
Oui, mais Windows PowerShell est notamment un bel outil parce qu’il permet d’être concis et d’enchaîner les commandes très facilement, au risque d’être un peu obscur pour le novice. Ce risque est – à mon humble avis – largement compensé par la clarté du « discours » qu’apporte le concept d’objets. Je trouve donc, à titre personnel, qu’il est tout aussi important dans un blog que les exemples de scripts ou de lignes de commande en PowerShell reflètent et mettent en avant cet intérêt du produit.
Cette petite mise au point faite, voici une version du script d’Arul utilisant non pas WMI mais la commandelette standard get-process. Cette version, vous l’aurez compris, s’efforce d’être la plus concise possible. Il doit y avoir moyen de faire plus court, mais je n’ai pas passé trop de temps à creuser l’affaire…
PS> $procs = gps notepad | sort starttime
PS> $procs[1..$procs.count] | % {$_.kill()}
Pour la lisibilité du texte, j’ai quand même gardé des espaces entre les différents éléments de la ligne. La plupart peuvent être enlevés à la saisie. Seuls ceux entre gps et notepad et entre sort et starttime doivent être conservés pour une exécution sans erreur.
L’utilisation de get-process à la place de WMI a un inconvénient principal : elle ne permet pas l’interrogation d’un système distant. En attendant la v2 de PowerShell ? (notez le point d’interrogation – non, je ne sais pas plus que vous de quoi la v2 sera faite)
Ah, et à propos de la v2… Si elle pouvait permettre d’accéder aux éléments d’un tableau en utilisant les syntaxes $array[..-2] et $array[1..] pour respectivement récupérer tous les éléments du premier à l’avant-dernier et du second au dernier, ce serait formidable ! Merci d’avance.
Janel
June 10 Script pour l'énumération des membres d'un groupe(billet édité le 5 août 2007: script renommé de get-member en get-groupmember)
Pour faire suite à mes précédents billets sur la gestion du groupe Administrateurs, voici une version un peu plus élaborée de la fonction get-members que j’avais créée pour l’occasion. Cette fois-ci, il s’agit d’un script, et je l’ai appelé get-groupmember pour respecter la convention de nommage de Windows PowerShell qui recommande d’utiliser le singulier plutôt que le pluriel dans les noms.
# get-groupmember.ps1
#
# Enumère les membres d'un groupe.
#
# Usage: get-groupmember $group [$server]
#
# $group est le nom du groupe à parcourir.
# $server est le nom du serveur qui contient le groupe.
#
# Si $server n'est pas précisé, la machine locale est interrogée.
# On peut préciser un domaine à la place d'un nom de serveur.
#
param ($group, $server = ".")
if (! $group) {
throw "Vous devez préciser le nom du groupe à parcourir."
}
$ADSIGroup = [ADSI]"WinNT://$server/$group"
foreach ($member in $ADSIGroup.Members()) {
$ADSIName = $member.GetType().InvokeMember("AdsPath","GetProperty",$null,$member,$null)
# Dans certains cas, on peut avoir un SID à la place du nom d'utilisateur.
# Dans ces cas-là, on affiche le SID sans chercher à reconstruire la
# chaîne domaine\utilisateur:
if ($ADSIName -match "[^/]/[^/]") {
[String]::Join("\", $ADSIName.Split("/")[-2..-1])
}
else {
$ADSIName.Split("/")[-1]
}
}
Janel June 09 Administrateurs ou Administrators?Dans mon billet précédent, je faisais des requêtes ADSI sur le groupe Administrateurs d’un serveur distant. Les requêtes utilisent le nom du groupe, ce qui « de facto » implique qu’on soit sûr que le système distant est en français et donc que le groupe s’appelle bien Administrateurs. Comment faire si vous voulez administrer un parc multilingue, avec des systèmes en français, d’autres en anglais, et d’autres encore en Dieu sait quelle autre langue ?
Dieu merci (encore lui !) Microsoft a attribué un SID identique à tous les groupes créés par défaut sur tout système Windows, quelle que soit sa langue d’installation. Par exemple, le groupe dont le SID est S-1-5-32-545 est bien le groupe Utilisateurs sur un système français et le groupe Users sur un système anglais, De la même façon, le groupe qui nous intéresse ici (Administrateurs ou Administrators) aura toujours pour SID S-1-5-32-544.
Pour une liste complète des SID créés par défaut, reportez-vous à l’article 163846 du support Microsoft.
On peut donc commencer le travail sur un serveur distant en l’interrogeant sur le nom de son groupe dont le SID est S-1-5-32-544. Ensuite, on n’aura plus qu’à utiliser ce nom pour les requêtes ADSI nécessaires. Il existe sans doute plusieurs moyens de faire cela, voici une méthode simple avec WMI :
PS> $server = "mce01" # mce01 est le nom du serveur distant
PS> $adminname = (gwmi –computername $server win32_group | where {$_.SID –eq “S-1-5-32-544”}).name
PS> $adminname
Administrateurs
Et voilà. La variable $adminname est désormais prête à l’emploi pour les requêtes ADSI que j’ai décrites dans mon précédent billet. La première requête s’écrira alors :
PS> $admin = [ADSI]"WinNT://$server/$adminname"
Amusez-vous bien !
Janel Gérer le groupe Administrateurs avec PowerShellMe revoici après quelques mois d’absence. J’espère que vous n’avez pas trouvé le temps trop long J
Je reprends la plume suite à une question posée récemment sur le groupe de discussion Microsoft US dédié à Windows PowerShell. Quelqu’un demandait comment ajouter des membres au groupe Administrateurs d’un serveur distant. C’est le genre de tâche qu’un administrateur système peut être amené à faire très régulièrement, ainsi que de supprimer des membres, ou plus prosaïquement vérifier les membres actuels du groupe.
La réponse implique ADSI, interface d’accès aux annuaires disponibles dans un environnement Microsoft. Les deux principaux annuaires disponibles par défaut sont l’Active Directory et la base de comptes locale d’un système. On pourrait également ajouter à la liste la base de comptes d’un domaine pré-AD, dont la technique d’accès avec ADSI est strictement identique à celle utilisée pour une base de comptes locale.
C’est justement cette base locale qui nous intéresse ici. En effet, le groupe Administrateurs d’un système est stocké dans sa base locale. Pour y accéder, on utilisera la syntaxe suivante :
PS> $server = "mce01" # mce01 est le nom du serveur distant
PS> $admin = [ADSI]"WinNT://$server/Administrateurs"
Attention : dans une requête ADSI, le préfixe (ici WinNT://) est sensible à la casse. Vous n’obtiendrez aucun résultat si vous écrivez "winnt://" ou "WINNT://". Cette contrainte est propre à ADSI, quel que soit le langage à partir duquel on l’utilise.
Que peut-on faire avec $admin ? Nous commencerons par le plus compliqué : énumérer ses membres. Je ne rentrerai pas dans les détails conceptuels qui rendent cette énumération compliquée. En guise d’illustration, voici un affichage des comptes membres du groupe Administrateurs dans sa version « brute » :
PS> $admin.Members() | foreach {$_.GetType().InvokeMember("AdsPath","GetProperty",$null,$_,$null)}
WinNT://EUROPE/mce01/Administrateur
WinNT://EUROPE/mce01/Janel
Essayez un simple $admin.Members() pour voir ce que ça donne, et si le cœur vous en dit explorez les objets pour comprendre pourquoi il faut passer par la syntaxe tarabiscotée ci-dessus.
Quoi qu’il en soit, cette syntaxe nous donne à peu près ce qu’on est en droit d’attendre. Le format des noms de comptes tels qu’ils nous sont retournés est très intéressant. Si l’on veut plus d’informations sur un de ces comptes, et notamment si l’un d’entre eux est un groupe dont on veut à son tour énumérer les membres, on pourra réutiliser son nom tel quel dans une nouvelle requête ADSI.
Si l’on veut voir les noms s’afficher sous un format plus conventionnel, comme par exemple "domaine\utilisateur" (où domaine peut être le nom de l’ordinateur si l’utilisateur est local), on pourra remanier le texte grâce à des fonctions comme Split() et Join() – par exemple :
PS> $admin.Members() | foreach {[String]::Join("\", $_.GetType().InvokeMember("AdsPath","GetProperty",$null,$_,$null).Split("/")[-2..-1])}
mce01\Administrateur
mce01\Janel
La saisie d’une telle ligne devient vite laborieuse, elle sera avantageusement remplacée par une fonction ou un script :
PS> function get-members ($group) {
>> $group.Members() | foreach {
>> [String]::Join("\", $_.gettype().invokemember("AdsPath","GetProperty",$null,$_,$null).split("/")[-2..-1])
>> }
>> }
>>
PS> get-members $admin
mce01\Administrator
mce01\Janel
Ok, maintenant je veux supprimer le compte Janel et à la place ajouter le compte Jacques. Tous deux sont des comptes locaux au serveur. La syntaxe est on ne peut plus simple :
PS> $admin.Remove("WinNT://Janel")
PS> $admin.Add(“WinNT://Jacques”)
Vérifions que les manipulations ont bien marché :
PS> get-members $admin
mce01\Administrateur
mce01\Jacques
Vous noterez la syntaxe abrégée que j’ai utilisée pour désigner un compte local au système : WinNT://Janel. Dans le contexte de la requête $admin.Remove(), l’utilisation de la base locale est implicite en l’absence d’un nom de domaine ou d’ordinateur explicite. Et quelle syntaxe aurais-je utilisée si j’avais voulu ajouter un compte membre d’un domaine ? Voici :
PS> $admin.Add("WinNT://EUROPE/BigBoss")
PS> get-members $admin
mce01\Administrateur
mce01\Jacques
EUROPE\BigBoss
Avec un peu d’huile de coude supplémentaire, vous pourrez mettre tous ces éléments dans un ou plusieurs scripts qui vous faciliteront considérablement la vie dans vos tâches d’administration quotidiennes.
Janel |
|
|