Meilleur auteur de réponses
Exécution de scripts PowerShell depuis un service

Question
-
Bonjour à toutes et à tous,
J'ai développé un Agent pour mon produit qui se présente sous la forme d'un service Windows exécuté sous le compte SYSTEM sans interaction avec le bureau (c'est une volonté).
Cet Agent a pour charge d'exécuter des commandes pour le compte d'utilisateurs authentifiés, via la fonction LogonUser(), puis CreateProcessAsUser().
Les besoins de cet Agent imposent une récupération (quasi) temps réel des flux de sortie de la commande. Pour cela, je crée des tubes, que je passe au niveau des informations de démarrage du processus (via la structure STARTUPINFO), en demandant une utilisation des Handles standards (STARTF_USESTDHANDLES).
Les attributs de la création du nouveau processus sont : NORMAL_PRIORITY_CLASS | CREATE_NO_WINDOW | CREATE_NEW_PROCESS_GROUP
Cela fonctionne très bien pour les commandes DOS (les commandes exécutées sont insérées dans un fichier .bat qui est lancé par cmd.exe), pour les exécutables de type "Application Console", mais pas avec les scripts PowerShell.
Il se trouve que dans le cas des scripts PowerShell, l'interpréteur PowerShell ne rend jamais la main, comme s'il attendait quelque chose. De plus, aucune donnée n'est envoyé sur l'un des flux standards.
Quelqu'un a-t-il rencontré ce genre de problème ?
Comment l'interpréteur PowerShell est-il supposé être invoqué ?
Bref, toute aide est la bienvenue.
Merci d'avance.
Réponses
-
Bonjour Gilles et Lionel,
Voici les conclusions de mes recherches, en plus de celles de Gilles :
Pour pouvoir exécuter un script PowerShell, il faut effectivement clore l'entrée standard de l'interpréteur powershell.exe.
Soit cela se fait par code, comme dans l'exemple cité plus haut, soit cela se fait dans la ligne de commande de l'interpréteur powershell.
Dans le cas où l'on souhaite fermer l'entrée standard de l'interpréteur en ligne de commande, il faut exécuter la commande comme suit :
powershell.exe "& 'c:\temp\test1.ps1'" <nul
La portion "<nul" permet de rediriger l'entrée standard de powershell.exe à partir de nul, qui est un "device" particulier permettant de clore l'entrée standard.
Cordialement,
Thomas
Toutes les réponses
-
-
Bonjour,
L'Agent crée un fichier bat comme celui-ci :
Code Snippet@echo off
set PATH=C:\Program Files\BV Associates\I-SIS_V203\Agent\_Isis\bin;C:\Program Files\BV Associates\I-SIS_V203\Agent\product\bin;C:\Program Files\BV Associates\I-SIS_V203\Core\ICles\PLUG\V1\bin;C:\Program Files\BV Associates\I-SIS_V203\Core\ICles\GENEWIN32\V1\core\bin;C:\Program Files\BV Associates\I-SIS_V203\Core\ICles\SYSTEM\WIN32\bin;C:\Program Files\BV Associates\I-SIS_V203\Core\ICles\_Isis\bin;C:\Program Files\BV Associates\I-SIS_V203\Core\ITools\bin;C:\WINDOWS\system32\WindowsPowerShell\v1.0;C:\Program Files\BV Associates\I-SIS_V203\Core\lib;C:\Perl\bin\;C:\Program Files\FileZilla FTP Client;C:\Program Files\Putty;C:\WINDOWS\system32;C:\WINDOWS;C:\WINDOWS\System32\Wbem;C:\Program Files\Bitvise Tunnelier;C:\Program Files\cvsnt;C:\WINDOWS\system32\WindowsPowerShell\v1.0
set BV_DEFPATH=C:\Program Files\BV Associates\I-SIS_V203\Agent\_Isis\def;C:\Program Files\BV Associates\I-SIS_V203\Core\ICles\PLUG\V1\def;C:\Program Files\BV Associates\I-SIS_V203\Core\ICles\GENEWIN32\V1\core\def;C:\Program Files\BV Associates\I-SIS_V203\Core\ICles\SYSTEM\WIN32\def;C:\Program Files\BV Associates\I-SIS_V203\Core\ICles\_Isis\def;C:\Program Files\BV Associates\I-SIS_V203\Core\ITools\def
set BV_TABPATH=C:\Program Files\BV Associates\I-SIS_V203\Agent\_Isis\tab;C:\Program Files\BV Associates\I-SIS_V203\Core\ICles\SYSTEM\_Core\tab;C:\Program Files\BV Associates\I-SIS_V203\Core\ICles\_Isis\tab;C:\Program Files\BV Associates\I-SIS_V203\Core\ITools\tab
set BV_PCIPATH=C:\Program Files\BV Associates\I-SIS_V203\Agent\_Isis\pci;C:\Program Files\BV Associates\I-SIS_V203\Core\ICles\PLUG\V1\pci;C:\Program Files\BV Associates\I-SIS_V203\Core\ICles\_Isis\pci;C:\Program Files\BV Associates\I-SIS_V203\Core\ICles\SYSTEM\WIN32\pci
set ISIS_CNXUSR=moyenne\isis
set IsisUser=moyenne\isis
set AdmView=FALSE
set EntityName=moyenne
set Role=ISIS
set AgentName=moyenne
set Key=moyenne\isis
set TableName=AgentAccess
set _command_=powershell.exe -Noninteractive -command "& {C:\\DEV\\test_powershell.ps1}"
set BV_PROC=C:\Program Files\BV Associates\I-SIS_V203\Agent\work\exec\_ISexc.3048.41.bat%_command_%
exit %errorlevel%Ce script bat est ensuite exécuté par la ligne de commande cmd.exe /c "<absolute path to the script>".
C'est cette dernière qui est exécutée via l'API CreateProcessAsUser() suite à un appel à ImpersonateLoggedOnUser().
Cordialement
-
-
-
Bonjour,
Oui, j'ai déjà essayé, et non, ça ne bloque pas au niveau du fichier de commande.
J'obtiens le résultat suivant :
Code SnippetC:\>set Role=ISIS
C:\>set AgentName=moyenne
C:\>set Key=moyenne\isis
C:\>set TableName=AgentAccess
C:\>set _command_=powershell.exe -Noninteractive -command "& {C:\\DEV\\test_powe
rshell.ps1}"C:\>set BV_PROC=C:\Program Files\BV Associates\I-SIS_V203\Agent\work\exec\_ISexc
.3048.41.batC:\>powershell.exe -Noninteractive -command "& {C:\\DEV\\test_powershell.ps1}"
AV
Dbut script de test PowerShelllundi 18 aot 2008 16:26:36
Unrestricted
Fin du script
C:\> -
-
Bonjour,
Je ne peux pas utiliser la commande RunAs, puisqu'elle me demande le mot de passe du compte SYSTEM, mot de passe que je ne connais pas (je suis pourtant administrateur sur ma machine).
J'ai ouvert une fenêtre cmd.exe sous le compte SYSTEM par le biais de la commande 'at', puis ai exécuté le script. Cela a également fonctionné.
Ce qui est perturbant avec le PowerShell, c'est le temps et les ressources consommées avant que la moindre instruction soit exécutée.
Bref, le script s'exécute à priori correctement. Il doit y avoir quelque chose qui le gène dans ma façon d'exécuter une commande. Pourtant, je n'ai aucun problème avec les commandes internes, style dir, les exécutables type console, les scripts bat, les scripts perl, les scripts VB, etc.
A l'heure actuelle, seul PowerShell semble poser des problèmes... Donc, j'en viens à penser naturellement qu'il a un comportement particulier.
Cordialement
-
-
Bonjour,
Ci-dessous le code permettant de reproduire le problème :
Code Snippet#include <windows.h>
#include <stdio.h>
#include <iostream.h>
int main() {
HANDLE standard_output_pipe_handle = INVALID_HANDLE_VALUE;
HANDLE standard_error_pipe_handle = INVALID_HANDLE_VALUE;
HANDLE standard_input_pipe_handle = INVALID_HANDLE_VALUE;
HANDLE std_out_handle;
HANDLE std_err_handle;
HANDLE std_in_handle;
PROCESS_INFORMATION process_information;
STARTUPINFO startup_information;
SECURITY_ATTRIBUTES security_attributes;
HANDLE tmp_handle;
//const char* command = "powershell.exe -Noninteractive -NoLogo -command \"date\"";
char* command = "powershell.exe -Noninteractive -command \"& {C:\\temp\\test1.ps1}\"";
DWORD number_read;
char buffer[1025];
setvbuf(stdout, NULL, _IONBF, 0);
security_attributes.nLength = sizeof(SECURITY_ATTRIBUTES);
security_attributes.lpSecurityDescriptor = NULL;
security_attributes.bInheritHandle = TRUE;
// Création du handle pour stdout
if(CreatePipe(&tmp_handle, &std_out_handle, &security_attributes, 0) == FALSE) {
cerr << "CreatePipe error : " << GetLastError() << endl;
return 1;
}
if(DuplicateHandle(GetCurrentProcess(), tmp_handle,
GetCurrentProcess(), &standard_output_pipe_handle, 0, FALSE,
DUPLICATE_SAME_ACCESS) == FALSE) {
cerr << "DuplicateHandle error : " << GetLastError() << endl;
return 1;
}
// On ferme le handle héritable
if(CloseHandle(tmp_handle) == FALSE) {
cerr << "CloseHandle error : " << GetLastError() << endl;
return 1;
}
// Création du handle pour stderr
if(CreatePipe(&tmp_handle, &std_err_handle, &security_attributes, 0) == FALSE) {
cerr << "CreatePipe error : " << GetLastError() << endl;
return 1;
}
if(DuplicateHandle(GetCurrentProcess(), tmp_handle,
GetCurrentProcess(), &standard_error_pipe_handle, 0, FALSE,
DUPLICATE_SAME_ACCESS) == FALSE) {
cerr << "DuplicateHandle error : " << GetLastError() << endl;
return 1;
}
// On ferme le handle héritable
if(CloseHandle(tmp_handle) == FALSE) {
cerr << "CloseHandle error : " << GetLastError() << endl;
return 1;
}
// Création du handle pour stdin
if(CreatePipe(&std_in_handle, &tmp_handle, &security_attributes, 0) == FALSE) {
cerr << "CreatePipe error : " << GetLastError() << endl;
return 1;
}
if(DuplicateHandle(GetCurrentProcess(), tmp_handle,
GetCurrentProcess(), &standard_input_pipe_handle, 0, FALSE,
DUPLICATE_SAME_ACCESS) == FALSE) {
cerr << "DuplicateHandle error : " << GetLastError() << endl;
return 1;
}
// On ferme le handle héritable
if(CloseHandle(tmp_handle) == FALSE) {
cerr << "CloseHandle error : " << GetLastError() << endl;
return 1;
}
// On prépare l'exécution
ZeroMemory(&process_information, sizeof(PROCESS_INFORMATION));
ZeroMemory(&startup_information, sizeof(STARTUPINFO));
startup_information.cb = sizeof(STARTUPINFO);
// On va indiquer qu'il faut utiliser les handles fournis
startup_information.dwFlags = STARTF_USESTDHANDLES;
startup_information.hStdOutput = std_out_handle;
startup_information.hStdError = std_err_handle;
startup_information.hStdInput = std_in_handle;
if(CreateProcess(NULL, command, NULL, NULL,
TRUE, NORMAL_PRIORITY_CLASS | CREATE_NO_WINDOW |
CREATE_NEW_PROCESS_GROUP , NULL, NULL,
&startup_information, &process_information) == FALSE) {
cerr << "CreateProcess error : " << GetLastError() << endl;
}
CloseHandle(std_out_handle);
CloseHandle(std_err_handle);
CloseHandle(std_in_handle);
WaitForSingleObject(process_information.hProcess, INFINITE);
CloseHandle(process_information.hProcess);
CloseHandle(process_information.hThread);
cout << "stdout : '";
while(true) {
// Lecture des tubes
if(ReadFile(standard_output_pipe_handle, buffer, 1024, &number_read, NULL) == FALSE ||
number_read == 0) {
if(GetLastError() == ERROR_BROKEN_PIPE) break;
cerr << "ReadFile error : " << GetLastError() << endl;
return 1;
}
if(number_read == 0) break;
buffer[(int)number_read] = 0;
cout << buffer << endl;
}
cout << "'" << endl;
CloseHandle(standard_output_pipe_handle);
cout << "stderr : '";
while(true) {
// Lecture des tubes
if(ReadFile(standard_error_pipe_handle, buffer, 1024, &number_read, NULL) == FALSE ||
number_read == 0) {
if(GetLastError() == ERROR_BROKEN_PIPE) break;
cerr << "ReadFile error : " << GetLastError() << endl;
return 1;
}
if(number_read == 0) break;
buffer[(int)number_read] = 0;
cout << buffer << endl;
}
CloseHandle(standard_error_pipe_handle);
cout << "'" << endl;
CloseHandle(standard_input_pipe_handle);
}
A noter que si on remplace la commande par "cmd.exe /c dir", par exemple, ça fonctionne très bien !
-
Bonjour,
La configuration des projets Visual C++ est assez complexe.
Est-il possible que vous puissiez m'envoyer un projet Visual C++ contenant ce code, compilant sans problème à : gilles.tourreau@pos.fr
Cordialement
-
-
Bonjour Lionel,
L'environnement est restitué tel quel dans le processus fils. Il n'y a pas de problème de ce côté là.
Après de plus amples discussions avec Gilles, et des tests supplémentaires, il s'avère que le PowerShell ne se termine que lorsque son entrée standard est fermée, ce qui est totalement aberrant dans le contexte d'une exécution d'un script.
L'interpréteur, dans ce cas, doit se terminer lorsque le script se termine, pas lorsque l'entrée standard est fermée.
Que celui-ci attende la fin de l'entrée en mode interactif se comprend, mais que le comportement soit strictement identique lorsque l'on souhaite juste exécuter un script ne se justifie pas. Si le script exécuté a besoin d'être alimenté par son entrée standard, soit, on attend d'avoir des données dessus, mais c'est au script de déterminer quand il se termine, pas à l'interpréteur.
Ce genre de fonctionnement ne peut pas être envisageable dans un environnement d'exploitation, ce pourquoi est supposé être destiné PowerShell. A mon sens, il y a là un problème de conception de l'interpréteur.
Pour résumer :
Pour pouvoir exécuter un script PowerShell dans le contexte énoncé dans le premier post, il faut clôturer l'entrée standard du script avant même d'avoir commencé par en capter les sorties standard et d'erreur.
La suite, du coup, viendra peut-être d'ailleurs...
Thomas
-
Bonjour Gilles et Lionel,
Voici les conclusions de mes recherches, en plus de celles de Gilles :
Pour pouvoir exécuter un script PowerShell, il faut effectivement clore l'entrée standard de l'interpréteur powershell.exe.
Soit cela se fait par code, comme dans l'exemple cité plus haut, soit cela se fait dans la ligne de commande de l'interpréteur powershell.
Dans le cas où l'on souhaite fermer l'entrée standard de l'interpréteur en ligne de commande, il faut exécuter la commande comme suit :
powershell.exe "& 'c:\temp\test1.ps1'" <nul
La portion "<nul" permet de rediriger l'entrée standard de powershell.exe à partir de nul, qui est un "device" particulier permettant de clore l'entrée standard.
Cordialement,
Thomas