Écrire des scripts sous Windows

par Lionel Fourquaux

Index

Si vous souhaitez apprendre à écrire des scripts sous Windows, cette page s'adresse à vous. Commençons tout de suite. (Attention, cette page est encore inachevée, et le restera sans doute car les technologies présentées ici peuvent souvent être remplacées avantageusement par .Net et/ou PowerShell).

Un premier script

Avec le Bloc-Note, ou votre éditeur de texte préféré, créez un fichier contenant la ligne suivante.

WScript.Echo("Hello, world!");

Enregistrez-le sous le nom hello.js et cliquez dessus. Et voilà, vous savez afficher un message à l'aide d'un script !

Comment ça marche ?

Quand vous cliquez sur ce fichier, Windows lance la commande wscript hello.js. Le programme wscript.exe est un hôte de script, qui va charger le moteur de script approprié (javascript) pour exécuter le fichier indiqué, et prédéfinir quelques objets comme WScript. Le moteur de script va lire le fichier hello.js, et appeler la méthode Echo de l'objet WScript, qui affiche alors le message indiqué.

Les hôtes de scripts qui seront discutés ici sont wscript.exe, son analogue en ligne de commande cscript.exe (que fait la commande cscript //nologo hello.js ?), et mshta.exe. Pour ce qui est des moteurs de script, Windows connaît par défaut deux langages : javascript (aussi appelé ECMAScript ou JScript) et VBScript (un dérivé de Visual Basic). L'exemple ci-dessus s'écrirait ainsi en VBScript. (Le fichier pourrait s'appeler hello.vbs).

WScript.Echo "Hello, world!"

Il est possible d'installer des moteurs de scripts pour d'autres langages, comme Perl ou Haskell. Par préférence personnelle, j'utiliserai ici du javascript.

Accéder au système de fichiers

Pour faire des choses intéressantes dans nos scripts, il va falloir créer des objets qui permettront d'interagir avec le système. Voici par exemple comment énumérer les fichiers contenus dans le répertoire C:\WINDOWS.

var fso = new ActiveXObject("Scripting.FileSystemObject");
var d = fso.GetFolder("C:\\WINDOWS");
var fc = new Enumerator(d.Files);
var str = "";
for (; ! fc.atEnd() ; fc.moveNext())
{
	str += fc.item().Name + "\n";
};
WScript.Echo(str);

La première ligne est sans doute la plus importante à retenir. Elle crée un nouvel objet Scripting.FileSystemObject. Cet objet est un composant COM scriptable qui permet d'accéder au système de fichiers. Le reste du script consiste juste à manipuler les interfaces fournies par ce composant : d'abord en créant un objet Folder correspondant au répertoire voulu, puis en énumérant les éléments de la collection Files.

Sur le même principe, voici un script qui extrait des informations d'un fichier texte (les adresses IP des machines dont un paquet a été rejeté par le pare-feu de Windows).

var fso = new ActiveXObject("Scripting.FileSystemObject");
var lgf = fso.OpenTextFile("C:\\WINDOWS\\pfirewall.log", 1);
var tbl = new ActiveXObject("Scripting.Dictionary");
while (! lgf.AtEndOfStream)	// enumerate the lines
{
	var li = lgf.ReadLine();
	li = li.replace(/#.*$/, "");	// discard comments
	li = li.replace(/\s+$/, "");
	var t = /^\S+\s+\S+\s+DROP\s+\S+\s+(\d+(?:\.\d+){3})\s/
		.exec(li);
	if (t != null)		// discard strange lines
		tbl.Item(t[1]) = null;
};
var ips = (new VBArray(tbl.Keys())).toArray();
var str = "";
var i = ips.length;
while (i != 0)
	str += ips[--i] + "\n";
WScript.Echo(str);

Doter un script d'une interface

Si certains scripts sont prévus pour fonctionner en arrière-plan, d'autres ont besoin de communiquer avec l'utilisateur. Dans ce cas, il est temps de songer à en faire un script HTA.

Un script HTA se présente comme une page HTML, mais son nom de fichier porte l'extension .hta. Du point de vue de la sécurité, il est traité comme un script : pas de restrictions d'accès, mais c'est un fichier exécutable à part entière. L'hôte de script associé est mshta.exe.

Pour modifier l'affichage, on procède comme sur une page web en utilisant le DOM ou les autres objets et méthodes fournis par Internet Explorer.

Comme exemple, voici un script HTA qui utilise WMI et du VML pour afficher la proportion de mémoire physique utilisée.

<html
    xmlns="http://www.w3.org/1999/xhtml"
    xmlns:v="urn:schemas-microsoft-com:vml"
    xml:lang="en" lang="en">
  <head>
    <title>Memory usage</title>
    <style type="text/css">
/* <![CDATA[ */
h1 { text-align: center; margin-bottom: 100px; }
div#gr { text-align: center; }
v\:* { behavior: url(#default#VML); }
#pie {
	position: static;
	width: 200px; height: 200px;
}
#rcirc {
	position: absolute;
	left: -100; top: -100;
	width: 200; height: 200;
}
#bpath {
	position: absolute;
	left: -100; top: -100;
	width: 200; height: 200;
}
/* ]]> */
    </style>
    <script language="javascript" type="text/javascript">
// <![CDATA[
function set_pie(x)
{
	var a = Math.ceil(100 * Math.sin(x * 2 * Math.PI));
	var b = -Math.floor(100 * Math.cos(x * 2 * Math.PI));
	bpath.path = "ns m 0 0 wa -100 -100 100 100 0 -100 "
			+ a + " " + b + " x e";
}

var perf;

function get_mem_used()
{
	perf.Refresh_();
	return perf.FreePhysicalMemory / perf.TotalVisibleMemorySize;
}

function init()
{
	var loc = new ActiveXObject("WbemScripting.SWbemLocator");
	var wmi = loc.ConnectServer("", "root\\cimv2");
	var ins = wmi.InstancesOf("Win32_OperatingSystem");
	perf = (new Enumerator(ins)).item();
	set_pie(get_mem_used());
	window.setInterval("set_pie(get_mem_used())", 1000);
}
// ]]>
    </script>
  </head>
  <body onload="init()">
    <h1>Memory usage</h1>
    <div id="gr">
      <v:group id="pie" coordsize="200,200" coordorigin="-100,-100">
        <v:oval id="rcirc" fillcolor="red" strokecolor="black">
        </v:oval>
        <v:shape id="bpath" fillcolor="blue"
            path="ns m 0 0 wt -100 -100 100 100 0 -100 1 -100 x e">
        </v:shape>
      </v:group>
    </div>
  </body>
</html>

Envoyer un mail

Pour envoyer un mail, la collection d'objets à connaître est CDO, en particulier l'objet CDO.Message.

var msg = new ActiveXObject("CDO.Message");
msg.From = "Moi <me@mycomputer.invalid>";
msg.To = "Dave <devnull@ens.fr>";
msg.Subject = "Essai avec CDO.Message";
msg.TextBody = "J'essaie mon nouveau script.\n";
msg.Send();

Pour envoyer le mail, CDO essaie par défaut d'utiliser le serveur SMTP inclus dans IIS, quand il est installé (par exemple pour un Windows XP Pro), ou les paramètres d'Outlook Express. S'il n'arrive pas à trouver les informations requises, l'envoi échoue. Dans ce cas, il faut indiquer explicitement un serveur SMTP ou un répertoire de messages en attente d'envoi. Voici comment indiquer explicitement un serveur SMTP dans l'exemple précédent.

var cfg = new ActiveXObject("CDO.Configuration");
var flds = cfg.Fields;
flds("http://schemas.microsoft.com/cdo/configuration/sendusing")
	= 2;
flds("http://schemas.microsoft.com/cdo/configuration/smtpserver")
	= "nef.ens.fr";
flds.Update();
var msg = new ActiveXObject("CDO.Message");
msg.Configuration = cfg;
msg.From = "Moi <me@mycomputer.invalid>";
msg.To = "Dave <devnull@ens.fr>";
msg.Subject = "Essai avec CDO.Message";
msg.TextBody = "J'essaie mon nouveau script.\n";
msg.Send();

L'objet CDO.Message permet bien entendu de construire des messages plus complexes, avec des pièces jointes. La méthode est expliquée ici.

Il est également possible d'utiliser CDO pour poster des messages sur un serveur NNTP.

CDO est aussi utilisé pour personaliser le traitement des mails par le serveur SMTP d'IIS.

Se connecter à un serveur HTTP

Pour aller chercher une page web, on peut se tourner vers l'objet WinHttp.WinHttpRequest. Ainsi, le script suivant télécharge la page d'accueil du serveur des élèves de l'ENS.

var req = new ActiveXObject("WinHttp.WinHttpRequest.5.1");
req.Open("GET", "http://www.eleves.ens.fr/");
req.Send();
WScript.Echo(req.ResponseText);

WinHTTP peut aussi construire des requêtes POST, ce qui peut être utile pour un script qui doit transmettre des données volumineures à un serveur.

Les connexions en HTTPS sont aussi supportées, permettant de disposer d'une connexion sécurisée en quelques lignes de script.

Microsoft Office et les scripts

Beaucoup de logiciels de la suite Microsoft Office sont écrits comme des collections de composants COM, qui peuvent être utilisés dans des scripts. C'est le cas en particulier de Word, Excel et Outlook. On peut trouver la documentation de ces objets dans le chapitre de MSDN concernant Office, ou dans l'aide des produits en question (dans la partie traitant de la programmation en Visual Basic, qui utilise les mêmes objets COM).

Voici un exemple de ce que peut faire un script pilotant Microsoft Excel.

var excel = new ActiveXObject("Excel.Application");
excel.DisplayAlerts = false;
excel.Visible = true;
var sheet = excel.Workbooks.Add().ActiveSheet;
WScript.Sleep(1000);
for (var i = 1 ; i < 30 ; i++)
	sheet.Cells(i,1).Value = Math.log(i);
WScript.Sleep(1000);
var chart = excel.ActiveWorkbook.Charts.Add();
chart.ChartType = -4101;
chart.SeriesCollection.Add(sheet.Range("A1:A29"));
WScript.Sleep(5000);
excel.Quit();

Dans le cas d'Outlook, certaines limitations ont été volontairement ajoutées pour compliquer l'écriture de virus. En particulier, l'accès à certaines propriétés provoque l'affichage d'une demande de confirmation. Si l'on veut éviter cela sans revenir à de la programmation en C ou C++, une solution est d'installer le composant COM Outlook Redemption de Dmitry Streblechenko.

Où trouver de la documentation ?

La documentation de référence se trouve dans MSDN Library, en particulier dans le chapitre sur les technologies de scripts. Les commandes cscript et wscript sont décrites dans l'aide de Windows, qui comprend un chapitre sur les scripts.

Une grande partie de la documentation disponible dans MSDN Library peut être installée localement, et fait partie du Platform SDK. Cela permet un accès beaucoup plus confortable à ces informations.

Le site des MVPs a quelques pages sur les scripts, comme celle d'Alex Angelopoulos. On trouve aussi des articles intéressants sur le TechNet, comme les Tales from the Script.