Loupe

[Windows 8(.1)] Contrôler une application Windows 8(.1) depuis une application tierce

Ce procédé, connu sous le nom d’UIA (User Interface Automation), existe depuis très longtemps et nous allons voir comment le mettre en place dans le cas d’applications Windows 8(.1).

La première chose à noter est que, pour pouvoir piloter une application Windows Store, il va être nécessaire de passer par du C++ car les applications Windows Store s’exécutent dans un contexte sécurisé qui n’est pas accessible en C#.

N’étant pas un expert C++, pardonnez toutes les erreurs syntaxiques/de bonnes pratiques que vous pourriez trouver dans le code ci-dessous Smile

 

Avant de pouvoir contrôler une application, il est nécessaire de la lancer. Pour cela, il est nécessaire d’utiliser IApplicationActivationManager et sa méthode ActivateApplication:

 

IApplicationActivationManager* paam = nullptr;

DWORD pid = 0;
HRESULT hr = E_FAIL;

hr = CoCreateInstance(CLSID_ApplicationActivationManager, nullptr, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&paam));
if(SUCCEEDED(hr) && paam != nullptr)
{
    hr = paam->ActivateApplication(appId, nullptr, AO_NONE, &pid);
    if(SUCCEEDED(hr))
    {
        // Automation
    }
}

 

Ainsi, le bout de code précédent permet de déclencer l’exécution d’une application identifiée par le paramètre appId.

Ce paramètre est initialisé comme tel:

 

LPCWSTR appId = L"winstore_cw5n1h2txyewy!Windows.Store";

 

Cette valeur, qui correspond à l’application Store de Windows 8, provient tout simplement de la base de registre et, plus précisément, de cet emplacement: HKEY_CLASSES_ROOT\ActivatableClasses\Package\:

 

image

 

Sous le noeud “Package”, vous retrouver l’ensemble des applications Windows Store installées sur la machine. Il vous suffit alors de déplier, pour l’application qui vous intéresse, le noeud “Server” pour avoir accès à la valeur de AppUserModelId:

 

image

 

Une fois l’application lancée, il est possible de la piloter en utilisant IUIAutomation. Pour cela, il faut commencer par récupérer la fenêtre principale, qui correspond au bureau Windows. Cette fenêtre, qui a l’avantage d’être le parent de toutes les autres fenêtres/applications qui s’exécutent, peut être récupérer en utilisant la méthode GetRootElement:

 

// Get "Desktop" root element for UI Automation
hr = CoCreateInstance(__uuidof(CUIAutomation8), nullptr, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&automation));
if(SUCCEEDED(hr) && automation != nullptr)
{
    hr = automation->GetRootElement(&pRootWindow);
    if(SUCCEEDED(hr) && pRootWindow != nullptr)
    {
        // Sleep for a while so we are sure the root window is found
        Sleep(1000);
        
        //
    }
}         

 

Une fois qu’on a accès à cette fenêtre principale, il suffit de requêter ces enfants pour accéder à la fenêtre de l’application que l’on a lancé et que l’on veut piloter (l’application Store dans notre cas):

 

LPCWSTR windowStoreWindowName = L"Store";
IUIAutomationElement* = FindElementByName(pRootWindow, windowStoreWindowName);
if(windowStoreRootWindow != nullptr)
{
    //
}

 

La méthode FindElementByName, qui prend en paramètre l’objet parent et le nom de l’élément auquel on souhaite accéder, est définit comme suit:

 

IUIAutomationElement* FindElementByProperty(IUIAutomationElement* parent, PROPERTYID propertyId, const std::wstring& name, TreeScope scope = TreeScope_Children)
{
    IUIAutomationElement* foundElement = nullptr;
    IUIAutomationCondition* nameCond = nullptr;
    
    BSTR bstrName = SysAllocStringLen(name.data(), name.size());
    
    VARIANT nameVar;
    nameVar.vt = VT_BSTR;
    nameVar.bstrVal = bstrName;

    automation->CreatePropertyCondition(propertyId, nameVar, &nameCond);
    
    parent->FindFirst(scope, nameCond, &foundElement);
    
    return foundElement;
}

IUIAutomationElement* FindElementByName(IUIAutomationElement* parent, const std::wstring& name, TreeScope scope = TreeScope_Children)
{
    return FindElementByProperty(parent, UIA_NamePropertyId, name, scope);
}

 

Pour récupérer le nom de l’objet que vous voulez manipuler, vous pouvez utiliser l’outil inspect.exe, disponible dans le SDK Windows 8:

 

image

 

A partir de ce moment, nous avons accès à la fenêtre de notre application et nous pouvons donc continuer à utiliser l’UI Automation pour la manipuler. Par exemple, si nous voulons avoir accès à la première tuile du Store, nous pouvons utiliser ce code:

 

// Find the first tile on the Store using its automation id property (named "tile_1")
IUIAutomationElement* firstTile = FindElementByProperty(windowStoreRootWindow, UIA_AutomationIdPropertyId, L"tile_1", TreeScope_Descendants);

 

On notera que, dans le cas précédent, nous avons récupérer la première tuile en utilisant la propriété AutomationId (don’t la valeur vient, là encore, de l’outil inspect.exe):

 

image

 

Ce choix de propriété, plutôt que l’utilisation de la propriété “Name”, est personnel/dépends des cas (dans la majorité des cas, nous passerons pas la propriété Name mais, dans le cas ci-dessus, celle-ci est composée d’espaces et de sauts à la ligne donc potentiellement non simple pour la lecture et mise en place du code).

Nous avons donc accès à notre application et à notre élément mais inspect.exe nous permet également de voir les différents “pattern” qui sont utilisables (ou non) pour cet élément:

 

image

 

Ainsi, on constate que “IsInvokePatternAvailable” est à “true”: il est donc possible d’invoquer cet élément donc, autrement dit de cliquer dessus! Pour cela, nous allons utiliser la méthode GetCurrentPatternAs, pour s’assurer que l’on récupère bien la possibilité de cliquer sur l’item et, si oui, alors on l’invoque grâce à la méthode Invoke:

 

// Get invoke pattern for the first tile
IUIAutomationInvokePattern* patternInvoke = nullptr;

hr = firstTile->GetCurrentPatternAs(UIA_InvokePatternId, IID_PPV_ARGS(&patternInvoke));
if(SUCCEEDED(hr) && patternInvoke != nullptr)
{
    // Invoke the "invoke" pattern: simulate a clic on the first tile
    hr = patternInvoke->Invoke();
    if(SUCCEEDED(hr))
    {
        //
    }
}

 

Et voilà! Si vous exécutez ce bout de code, vous constaterez que le Windows Store se lance et qu’un clic sur la première tuile est effectué automatiquement!

Ah, et parce qu’on est en C++ et qu’il faut penser à faire le ménage, n’oubliez de libérer les ressources Winking smile

 

if(windowStoreRootWindow)
{
    windowStoreRootWindow->Release();
    windowStoreRootWindow = nullptr;
}

if(pRootWindow)
{
    pRootWindow->Release();
    pRootWindow = nullptr;
}

if(automation)
{
    automation->Release();
    automation = nullptr;
}

if (paam) 
{
    paam->Release();
    paam = nullptr;
}

 

Libre à vous maintenant d’enrichir ce code et de créez vos propres scénarios dans lesquels l’UI Automation de vos applications Windows 8(.1) vous sera utiles !

 

Happy coding! Smile

Ces billets pourraient aussi vous intéresser

Vous nous direz ?!

Commentaires

comments powered by Disqus