none
Exécution de scripts PowerShell depuis un service RRS feed

  • 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.

    lundi 18 août 2008 10:20

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

    mardi 23 septembre 2008 10:27

Toutes les réponses

  • Bonjour,

     

    Est-il possible que vous puissiez montrer comment vous lancez le script PowerShell ?

     

    Cordialement

     

    lundi 18 août 2008 10:37
    Modérateur
  • 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

    lundi 18 août 2008 12:57
  • J'ai oublié de fournir le contenu du script PowerShell :

    Code Snippet
    "Début script de test PowerShell"
    Get-Date
    Get-ExecutionPolicy
    "Fin du script"

     

     

     

    Voilà qui est fait.

    lundi 18 août 2008 14:09
  • Bonjour,

     

    Avez-vous essayé d'exécuter votre fichier de commande directement sur une console (en enlevant @ECHO OFF) ?

    Est-ce que celui-ci bloque aussi au niveau du fichier de commande ?

     

    Cordialement

     

    lundi 18 août 2008 14:18
    Modérateur
  • 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 Snippet

    C:\>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.bat

    C:\>powershell.exe -Noninteractive -command "& {C:\\DEV\\test_powershell.ps1}"
    AV
    Dbut script de test PowerShell

    lundi 18 aot 2008 16:26:36
    Unrestricted
    Fin du script


    C:\>

     

     

    Cordialement
    lundi 18 août 2008 14:27
  • Bonjour,

     

    Avez-vous essayé de lancer votre fichier de commande sous le compte SYSTEM ?

    (Utilisez la commande "RUNAS")

     

    Cordialement

    lundi 18 août 2008 19:10
    Modérateur
  • 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

    mardi 19 août 2008 09:32
  • Bonjour,

     

    Est-il possible que vous puissez m'envoyer un projet simple reproduisant le problème ?

     

    Cordialement

     

    mardi 19 août 2008 09:34
    Modérateur
  • 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 !

    jeudi 21 août 2008 12:52
  • 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

     

    vendredi 22 août 2008 09:43
    Modérateur
  • Bonjour, je n'ai pas fait de tests, mais cela me rappelle le problème d'environnement du cmd non hérité par l'exécution lancée.
    Vérifier si les variables d'environnement sont renseignées.

    La suite m'intéresse aussi...
    Lionel
    mercredi 3 septembre 2008 11:43
  • 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

    mercredi 3 septembre 2008 12:33
  • 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

    mardi 23 septembre 2008 10:27