Windows Management Instrumentation avec du code C++

 

Il y a quelques semaines, j’ai travaillé sur un bout de code, afin d’activer le contrôle parental d’un compte utilisateur sur Windows 8.1. Naïvement je pensais qu’il existait une API Win32 simple pour le faire du style :

BOOL EnableParentalControl(LPWSTR username).

Mais en y regardant de plus près, cette simple API n’existe pas. En fouillant plus en profondeur, il faut passer par l’infrastructure Windows Management Instrumentation (WMI).

Ce billet a donc juste pour vocation, non pas de vous expliquer ce qu’est WMI, mais à vous montrer comment faire un appel WMI en C++. Vous allez voir c’est existant.

Tout d’abord, il faut savoir ou trouver les différentes informations, espace de nom, signature des méthodes, propriétés, en gros le schéma utile pour nos différents appels. Pour le contrôle parental, il faut ouvrir le fichier wpcsprov.mof, dans le répertoire c:\windows\system32.

Dans ce fichier, vous pourrez y trouver l’espace de nom racine d'où nous partirons pour effectuer nos requêtes.
#pragma namespace(\\\\.\\ROOT\\CIMV2\\Applications\\WindowsParentalControls)

Nous y retrouverons le nom de la classe WpCSystemSettings, avec ces méthodes associées AddUser et RemoveUser et leurs signatures.

//**************************************************************************
//* Class: WpcSystemSettings
//* Derived from:
//**************************************************************************
[Singleton: DisableOverride ToInstance ToSubClass, Description("Stores system-wide Wpc settings"): ToInstance ToSubClass, provider("WpcSProv"): ToInstance, dynamic: ToInstance]
class WpcSystemSettings
{
[read: ToInstance ToSubClass, write: ToInstance ToSubClass, Description("Link to the current games rating system in force for the machine"): ToInstance ToSubClass] string CurrentGamesRatingSystem;
[read: ToInstance ToSubClass, write: ToInstance ToSubClass, Description("HTTP app filter exemption list"): ToInstance ToSubClass] string HTTPExemptionList[];
[read: ToInstance ToSubClass, Description("Windows HTTP app filter exemption list"): ToInstance ToSubClass] string WinHTTPExemptionList[];
[read: ToInstance ToSubClass, write: ToInstance ToSubClass, Description("HTTP URL filter exemption list"): ToInstance ToSubClass] string URLExemptionList[];
[read: ToInstance ToSubClass, Description("Windows HTTP URL filter exemption list"): ToInstance ToSubClass] string WinURLExemptionList[];
[read: ToInstance ToSubClass, write: ToInstance ToSubClass, Description("Number of days until a balloon reminder for checking the logs"): ToInstance ToSubClass] uint32 LogViewReminderInterval;
[read: ToInstance ToSubClass, write: ToInstance ToSubClass, Description("Last time the admin checked the log viewer"): ToInstance ToSubClass] DateTime LastLogView;
[read: ToInstance ToSubClass, write: ToInstance ToSubClass, Description("ID of current web filter"): ToInstance ToSubClass] string FilterID;
[read: ToInstance ToSubClass, write: ToInstance ToSubClass, Description("Name of current web filter"): ToInstance ToSubClass] string FilterName;
[Description("Creates settings for a new User SID"): ToInstance ToSubClass, Implemented: ToInstance ToSubClass, Static: ToInstance ToSubClass] uint32 AddUser([Description("SID to create settings for"): ToInstance ToSubClass, IN] string strSID);
[Description("Removes settings for a user account"): ToInstance ToSubClass, Implemented: ToInstance ToSubClass, Static: ToInstance ToSubClass] uint32 RemoveUser([Description("SID of the account to delete settings for"): ToInstance ToSubClass, IN] string strSID);
};

Puis la classe WpcUserSettings, qui possède les propriétés et leurs signatures que nous utiliserons pour paramétrer et activer le contrôle parental.

//**************************************************************************
//* Class: WpcUserSettings
//* Derived from:
//**************************************************************************
[Description("Per-account Windows Parental Controls Settings"): ToInstance ToSubClass, provider("WpcSprov"): ToInstance, dynamic: ToInstance]
class WpcUserSettings
{
[key, read: ToInstance ToSubClass, Description("SID of the user account owning these settings"): ToInstance ToSubClass] string SID;
[read: ToInstance ToSubClass, write: ToInstance ToSubClass, Description("Is WPC enabled for this user"): ToInstance ToSubClass] boolean WpcEnabled;
[write: ToInstance ToSubClass, Description("Is logging enabled for this user"): ToInstance ToSubClass, read: ToInstance ToSubClass] boolean LoggingRequired;
[write: ToInstance ToSubClass, Description("Are hourly restrictions enabled for this user"): ToInstance ToSubClass, read: ToInstance ToSubClass] boolean HourlyRestrictions;
[write: ToInstance ToSubClass, Description("Are time allowance restrictions enabled for this user"): ToInstance ToSubClass, read: ToInstance ToSubClass] boolean TimeAllowanceRestrictions;
[write: ToInstance ToSubClass, Description("Are override requests enabled for this user"): ToInstance ToSubClass, read: ToInstance ToSubClass] boolean OverrideRequests;
[write: ToInstance ToSubClass, Description("Logon Hours mask for this user"): ToInstance ToSubClass, read: ToInstance ToSubClass] uint32 LogonHours[7];
[write: ToInstance ToSubClass, Description("Logon Half-Hours (30 minute offset) mask for this user"): ToInstance ToSubClass, read: ToInstance ToSubClass] uint32 LogonHalfHours[7];
[write: ToInstance ToSubClass, Description("Daily minute allowance"): ToInstance ToSubClass, read: ToInstance ToSubClass] uint32 AllowanceMinutes[7];
[write: ToInstance ToSubClass, Description("Are application restrictions enabled for this user"): ToInstance ToSubClass, read: ToInstance ToSubClass] boolean AppRestrictions;
};

Une fois que l’on a compris que tout est dans ce fichier, on peut commencer à coder.

WMI étant une API issue du monde COM, il faut initialiser COM.

::CoInitializeEx(NULL, COINIT_APARTMENTTHREADED);

Ensuite comme il est indiqué dans l’exemple MSDN suivant Getting WMI Data from the Local Computer, il faudrait également initialiser la sécurité COM avec l’API suivante :

CoInitializeSecurity(
        NULL,      // Security descriptor
        -1,      // COM negotiates authentication service
        NULL,      // Authentication services
        NULL,      // Reserved
        RPC_C_AUTHN_LEVEL_DEFAULT, // Default authentication level for proxies
        RPC_C_IMP_LEVEL_IMPERSONATE, // Default Impersonation level for proxies
        NULL,      // Authentication info
        EOAC_NONE,     // Additional capabilities of the client or server
        NULL);      // Reserved

Néanmoins, mon code faisant partie d’un projet beaucoup plus vaste qui sera appelé à partir d’une application C# .NET et comme chacun sait, le CLR de .NET a déjà initialisé COM et sa sécurité pour nous, il n’est donc pas forcément nécessaire de le faire. Tout dépend du contexte.

Venons-en à WMI proprement dit.

La première chose à faire est d’initialiser un IWbemLocator

  HRESULT hr = S_OK;
  CComPtr<IWbemLocator> pLocator;
  hr=pLocator.CoCreateInstance(CLSID_WbemLocator,
                               NULL, CLSCTX_INPROC_SERVER | CLSCTX_LOCAL_SERVER);
  if (FAILED(hr))
  {          
      throw StatusException(NERR_CouldNotEnableParentalControl);
  }

J’utilise ici directement un smart pointeur CComPtr qui permet d’éliminer les fuites mémoires en effectuant automatiquement le décompte de références.

Ensuite il faut se connecter au service WMI qui nous intéresse, c’est à dire le contrôle parental. Ce service est symbolisé ici par l’espace de nom :

\\\\.\\ROOT\\CIMV2\\Applications\\WindowsParentalControls

CComPtr<IWbemServices> piWmiServices;
      BSTR bstrServiceNameSpace = SysAllocString(L"\\\\.\\root\\CIMV2\\Applications\\WindowsParentalControls");
      //Connect to the server
      hr=pLocator->ConnectServer(bstrServiceNameSpace,
          NULL,                    // User name. NULL = current user
          NULL,                    // User password. NULL = current
          NULL,                    // Locale. NULL indicates current
          NULL,                    // Security flags.
          0,                       // Authority (e.g. Kerberos)
          NULL,                    // Context object
          &piWmiServices            // pointer to IWbemServices proxy
          );
      if (FAILED(hr))
      {          
          throw StatusException(NERR_CouldNotEnableParentalControl);
      }

Note : Ne pas oublier de libérer la chaine BSTR une fois utilisée SysFreeString(bstrServiceNameSpace)

La méthode ConnectServer retourne un proxy vers une interface IWbemServices. Ce proxy nous servira pour tous les autres appels. Mais comme nous sommes dans un contexte qui s’exécutera en RPC, il est important avant tout, de pouvoir se faire passer comme la bonne autorité et pour ce faire nous allons utiliser la méthode CoSetProxyBlanket sur notre proxy.

HRESULT hr = S_OK;
      hr=CoSetProxyBlanket(piWmiServices,
          RPC_C_AUTHN_WINNT,
          RPC_C_AUTHZ_NONE,
          NULL,
          RPC_C_AUTHN_LEVEL_CALL,
          RPC_C_IMP_LEVEL_IMPERSONATE,
          NULL,
          EOAC_NONE
          );
      if (FAILED(hr))
      {          
          throw StatusException(NERR_CouldNotEnableParentalControl);
      }

Sans cet appel, tous les autres appels échoueront.

Maintenant que nous avons notre proxy en bon état de marche, nous allons pouvoir effectuer nos premières requêtes.

La première étant d’exécuter la méthode AddUser, mentionnée plus haut dans le fichier wpcsprov.mof, pour la classe WpcSystemSettings.

Tout d’abord il faut obtenir une instance de cette classe. Nous allons utiliser la méthode GetObject , de notre proxy, en lui passant comme paramètre le nom de la classe sous forme d’une chaine de caractères.

L’instance sera retournée en tant qu’interface IWbemClassObject

HRESULT hr = S_OK;
      CComPtr<IWbemClassObject> pObjectWpcSystemSettings;
      BSTR bstrPath = SysAllocString(L"WpcSystemSettings");
      hr=piWmiServices->GetObject(bstrPath, 0, NULL, &pObjectWpcSystemSettings, NULL);

      if (FAILED(hr))
      {          
          throw StatusException(NERR_CouldNotEnableParentalControl);
      }

Ensuite sur l’instance de cette classe, nous allons devoir récupérer pour la méthode AddUser, une autre instance de l’interface IWbemClassObject qui définira les paramètres d’entrée de la méthode. (En regardant dans le fichier mof, cette méthode prend comme paramètre une chaine nommée strSID. Notez le nom il aura son importance par la suite)

uint32 AddUser([Description("SID to create settings for"): ToInstance ToSubClass, IN] string strSID);

Note : Nous n’utiliserons pas le paramètre de retour de type uint32.

CComPtr<IWbemClassObject> piObjectSignatureIn;
      CComPtr<IWbemClassObject> piObjectSignatureOut;
      BSTR bstrMethod = SysAllocString(L"AddUser");
      hr=pObjectWpcSystemSettings->GetMethod(bstrMethod, 0, &piObjectSignatureIn, &piObjectSignatureOut);

      if (FAILED(hr))
      {          
          throw StatusException(NERR_CouldNotEnableParentalControl);
      }

La méthode GetMethod , récupère une instance de la classe IWbemClassObject (piObjectSignatureIn) pour la méthode AddUser, qui lui est passée en paramètre en tant que chaine de caractères.

Nous allons utiliser l’instance piObjectSignatureIn, afin de lui affecter le paramètre strSID avec la bonne valeur.

HRESULT hr = S_OK;
      VARIANT varSID;
      LPWSTR SID = ::GetUserSID(L"MonServeur", L"NomUtilisateur");
      InitVariantFromString(SID, &varSID);
      
      piObjectSignatureIn->Put(L"strSID", 0, &varSID, 0);

      if (FAILED(hr))
      {          
          throw StatusException(NERR_CouldNotEnableParentalControl);
      }
      VariantClear(&varSID);

La méthode Put de notre instance de classe pousse pour le paramètre strSID, la valeur de notre SID.

Note : Il est possible de vérifier si cela a bien fonctionné en utilisant la méthode GetObjectText de notre instance.

BSTR debug;
        hr = piObjectSignatureIn->GetObjectText(0, &debug);
        

Vous devriez obtenir un texte au format mof qui ressemble à cela

[abstract]class __PARAMETERS
{
[Description("SID to create settings for"): ToInstance ToSubClass, IN, ID(0): DisableOverride ToInstance]
string strSID = "S-1-5-21-203241617-564013427-62682210-1111";
};

Un SID (Security Identifier) dans Windows est une valeur unique qui permet d’identifier par exemple un utilisateur.

Voici la méthode qui permet de récupérer un SID pour un utilisateur donné.

LPWSTR GetUserSID(LPWSTR servername, LPWSTR username)
{
    DWORD dwStatus = NERR_Success;
      LPUSER_INFO_4lpBufUserInfo = nullptr;
    DWORD LevelUserInfo = 4;
    LPWSTR sid = nullptr;
    try
    {
        //Get the User's SID
        CheckNERRStatusAndThrowNERRStatus(NetUserGetInfo(servername, username, LevelUserInfo, (LPBYTE*) &lpBufUserInfo));
        PSID CurrentPSID = lpBufUserInfo->usri4_user_sid;
        ConvertSidToStringSid(CurrentPSID, &sid);
        //Check if we have an error converting the string
        CheckNERRStatusAndThrowNERRStatus(GetLastError());
    }
    catch (StatusException ex)
    {
        dwStatus = ex.GetStatus();
    }
    if (lpBufUserInfo != NULL)
    {
        NetApiBufferFree(lpBufUserInfo);
    }
    return sid;
}

Une fois que la propriété est bien initialisée, on peut alors exécuter la méthode AddUser

HRESULT hr = S_OK;
      BSTR bstrPath = SysAllocString(L"WpcSystemSettings");
      BSTR bstrMethod = SysAllocString(L"AddUser");

      piWmiServices->ExecMethod(bstrPath, bstrMethod, 0, NULL, piObjectSignatureIn, NULL, NULL)

      if (FAILED(hr))
      {          
          throw StatusException(NERR_CouldNotEnableParentalControl);
      }

La dernière chose à faire maintenant est d’activer le contrôle parental pour cet utilisateur.

HRESULT hr = S_OK;
      CComPtr<IEnumWbemClassObject> piEnumerator;
      CComPtr<IWbemClassObject> piEnableWPC;
      VARIANT varEnableParentalControls;
      ULONG uReturned = 0;
      LPWSTR SID = ::GetUserSID(L"NomServeur", L"Nomutilisateur");
      CComBSTR bstrQuery(L"Select * from WpcUserSettings where SID='");
      BSTR bstrWQL = SysAllocString(L"WQL");
      
      bstrQuery.Append(SID);
      bstrQuery.Append(L"'");
     hr= piWmiServices->ExecQuery(bstrWQL, bstrQuery, WBEM_FLAG_FORWARD_ONLY,
                                   NULL, &piEnumerator)
     ///FAILED(hr)
     hr=piEnumerator->Next(0, 1, &piEnableWPC, &uReturned);
     ///FAILED(hr)
     hr=InitVariantFromBoolean(TRUE, &varEnableParentalControls);     
     ///FAILED(hr)
     hr=piEnableWPC->Put(L"WpcEnabled", 0, &varEnableParentalControls, 0);
     ///FAILED(hr)
     hr=piWmiServices->PutInstance(piEnableWPC, 0, NULL, NULL);

     SysFreeString(bstrWQL);     
     SysFreeString(bstrQuery);     
     VariantClear(&varEnableParentalControls);
     ///....

Pour ce faire on utilise la méthode ExecQuery de notre instance de service WMI. On parle ici bien d’une requête qui ressemble à du SQL, mais qui est en fait au format WQL (SQL pour WMI)

Cette méthode va retrouver tout simplement en fonction du SID de l’utilisateur une instance d’un enumérateur IEnumWbemClassObject sur la classe WpcUserSettings.

Ensuite on instancie la propriété au travers de l’interface IWbemClassObject (piEnableWPC) avec la méthode Next.

On indique que l’on veut activer le contrôle parental à l’aide de la propriété WpcEnabled que l’on positionne à TRUE, via la méthode Put.

Enfin on active réellement la propriété à l’aide de la méthode PutInstance de l’instance de notre service et le tour est joué.

Vous me direz beaucoup de travail, pour une simple action Sourire

 

Eric