PI Services

Le blog des collaborateurs de PI Services

Active directory : problème d'authentification par smartcard

Un de nos clients utilise des cartes à puce pour s'authentifier sur les postes de travail et depuis quelques mois, un problème apparemment aléatoire se produit : lorsque certaines cartes sont associées à certains comptes Active Directory, l'authentification ne fonctionne pas (erreur "invalid credentials" lors d'une tentative d'ouverture de session Windows).

Les mêmes cartes associées à d'autres comptes ne posent cependant aucun problème, et réciproquement.

La seule autre indication dont nous disposons au moment de commencer les investigations est un évènement 4771 généré dans le journal Security des contrôleurs de domaine. Il indique une erreur 0x42, qui se traduit par KRB_ERR_CERTIFICATE_MISMATCH (et non pas KRB_AP_ERR_USER_TO_USER_REQUIRED comme une première recherche nous l'avait fait penser, nous entraînant sur une fausse piste!)

Dernière information pertinente : les certificats présents sur ces cartes à puce ne contiennent pas l'UPN du compte qui les utilise pour ouvrir une session. Il s'agit de certificats génériques, fournis par une entité tierce.

Il est donc nécessaire de peupler la propriété LDAP altSecurityIdentities pour lier le compte Active Directory d'un utilisateur à une smartcard.

Première vérification évidente : les comptes utilisateurs contiennent bien une référence au certificat présent dans la smartcard dans cette propriété altSecurityIdentities.

Il s'agit plus précisément de leur propriété RFC822, autrement dit un identifiant au format adresse email.

L'erreur "Certificate Mismatch" ne semble donc pas être pertinente ici, mais une nouvelle recherche permet de trouver une note récente de Microsoft ( https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-winerrata/85d75079-92de-47e6-a1c1-7e4fd7f27a10 ) qui indique que depuis la mise à jour mensuelle de Mai 2022, les informations utilisées pour remplir la propriété altSecurityIdentities sont divisées en deux catégories "forte" et "faible"; que l'adresse RFC822 fait partie des "faibles" et que lors de l'utilisation d'une information "faible", l'erreur Certificate Mismatch "peut" être présente.

On progresse donc, mais il nous reste à comprendre quelles sont les conditions exactes pour obtenir cette erreur puisque par ailleurs la majorité des comptes fonctionne avec la majorité des cartes à puce sans problème, en utilisant le même identifiant RFC822 "faible".

Un nouvel événement (id 40 dans le journal System des contrôleurs de domaine) nous met la puce à l'oreille :

The Key Distribution Center (KDC) encountered a user certificate that was valid but could not be mapped to a user in a strong way (such as via explicit mapping, key trust mapping, or a SID). The certificate also predated the user it mapped to, so it was rejected

Il confirme clairement ce dont nous nous doutions déjà, à savoir que le certificat est valide mais pas associé au compte AD de façon sûre.

Mais il indique également un élément supplémentaire : s'il a été rejeté, c'est parce qu'en plus la date d'émission du certificat est antérieure à la date de création du compte.

Il s’agit d’un blocage destiné à permettre l’évolution progressive de la configuration de vos altSecurityIdentities : en effet, Microsoft indique que toutes les authentifications par certificat lié par une association « faible » échouera à partir de Mai 2023 (cf. KB5014754: Certificate-based authentication changes on Windows domain controllers - Microsoft Support ), et ce blocage permet donc d’éviter de créer des associations qui poseront problème après quelques mois seulement.

Deux solutions sont alors disponibles : 

· Regénérer le certificat de façon à ce qu’il contienne l’extension SID (introduite également lors de la mise à jour de Mai 2022), mais cette solution ne fonctionne que pour les certificats signés par une PKI Microsoft et dont les informations sont récupérées dans l’AD et non pas fournies dans la requête

· Changer les altSecurityIdentities de façon à établir une association forte.

Dans notre contexte, seule la seconde solution est possible puisque les certificats sont fournis par une entité tierce. 

Le format recommandé par Microsoft pour établir une liaison forte est le suivant : X509:<I>IssuerName<SR>1234567890

Il contient le nom de l’autorité de certification (au format Distinguished Name) ainsi que le numéro de série du certificat.

Attention : ces deux informations doivent être indiquées « à l’envers ». Par exemple, si le DN de l’autorité émettrice est CN=CONTOSO-DC-CA, DC=contoso, DC=com et le numéro de série du certificat est 2B 3C F4, alors le champ altSecurityIdentities doit contenir la chaine suivante : X509:<I>DC=com,DC=contoso,CN=CONTOSO-DC-CA<SR>F43C2B

Une fois cette modification effectuée, la connexion à l’aide de la smartcard fonctionne enfin !

NPS 2019 - Tous les paquets Radius sont ignorés

Le rôle NPS (Network Policy Server) reste le seul moyen natif d’utiliser un serveur Windows pour réaliser des authentifications RADIUS, ce qui est très utile notamment pour gérer l’authentification à des appliances réseau, des bornes Wifi ou même des utilisations plus avancées comme de l’authentification par certificat avec AlwaysOn VPN.

Bien qu’il n’ait pas évolué depuis plusieurs versions de Windows et que sa partie NAP (Network Access Protection) soit dépréciée depuis Windows 2012 R2, il reste parfaitement fonctionnel pour son rôle de serveur RADIUS.

J’ai donc récemment déployé un serveur NPS sous Windows 2019 sur un serveur flambant neuf, configuré mon client Radius (une appliance NTP) ainsi qu’une stratégie d’authentification basique pour pouvoir utiliser des identifiants de l’AD pour me connecter à l’appliance.

Malheureusement, le premier essai de connexion fut un échec. Pas d’affolement, les stratégies NPS sont souvent assez obscures et il est vite arrivé de manquer un paramètre, me dis-je… mais pas cette fois.

J’active donc les logs d’audit de connexion, ils permettent souvent d’en apprendre plus sur les raisons de l’échec. Sauf que cette fois, ils sont intégralement vides : non seulement il n’y a pas d’erreur, mais il n’y a en réalité pas le moindre évènement, comme si la requête RADIUS n’arrivait jamais au serveur.

Les règles de firewall Windows créée automatiquement lors de l’installation du rôle NPS (groupe Network Policy Server) sont pourtant bien présentes et actives, et l’administrateur réseau me confirme que les paquets arrivent bien jusqu’au serveur.

Il est donc temps de sortir Wireshark : effectivement, les paquets RADIUS arrivent bien au serveur mais ensuite rien, aucune réaction; encore une fois comme si la requête n’arrivait jamais au rôle NPS…

Après bien des tentatives infructueuses, j’essaye en désespoir de cause de désactiver le firewall Windows; et miracle, tout tombe en marche.

Le problème se situe donc au niveau du firewall. Après quelques recherches, il apparaît que les règles natives sont configurées pour ne fonctionner que pour le service IAS, ce qui est normal. Par contre, dans Windows 2019, ce service utilise un identifiant de sécurité (service SID) qui l’empêche d’être la cible d’une règle de firewall… et donc la règle ne fonctionne pas et les flux sont bloqués.

Deux solutions s’offrent donc à nous :

  • Reconfigurer le service pour retirer cette restriction à l’aide de la commande sc.exe sidtype IAS unrestricted
  • Reconfigurer les règles de firewall pour qu’elles fonctionnent avec n’importe quel service :  Get-NetFirewallRule -DisplayGroup "Network Policy Server" | where DisplayName -like "*RADIUS*" | Set-NetFirewallRule -Service Any

Après avoir exécuté une de ces deux solutions, tout devrait rentrer dans l’ordre !

RDS - Configurer le rôle License Server en ligne de commande et sans internet

Dans les environnements modernes, il est de plus en plus fréquent de rencontrer des serveurs déployés en mode “Server Core” (sans interface graphique) et bien sûr sans accès à Internet.

Le rôle RDS License Server est supporté sur ce type de serveur, mais son installation n’est pas aussi bien documentée qu’en mode graphique…

Il serait bien sûr possible de passer par une console installée sur un serveur de rebond, mais ca serait bien moins amusant! Et les informations suivantes peuvent également servir dans le cadre d’un déploiement automatisé par Ansible ou Powershell DSC.

Activation du serveur

Il est dans un premier temps nécessaire d’activer la fonctionnalité License Server. Le serveur n’ayant pas accès à Internet, il n’est pas possible de procéder à une activation automatique.

Il est cependant possible d’obtenir la clé d’activation à partir d’un autre poste disposant d’un accès à Internet :

  1. Récupérez le ProductId du License Server à l’aide de la commande suivante :
    (Get-WmiObject Win32_TSLicenseServer -Property ProductId).ProductId
    image

  1. Rendez-vous sur https://activate.microsoft.com/ et sélectionnez l’option Activate a License Server

  1. Indiquez le ProductId obtenu précédemment ainsi que le reste des informations demandées :
    image

  1. Après validation de vos informations, le site renvoie le License Server Id nécessaire à la poursuite de l’activation :
    image

  1. L’activation peut désormais être finalisée à l’aide des commandes suivantes (pensez à remplacer le License Server Id par la valeur obtenue précédemment, en supprimant les tirets)
    $serverId = 'VTXXXXXXXXXXXXXXXXX'
    $wmiClass = ([wmiclass]"\\localhost\root\cimv2:Win32_TSLicenseServer")
    $wmiClass.ActivateServer($serverId)

  1. En cas de réussite de l’opération, la valeur retournée pour ActivationStatus doit être 0 :
    image


Ajout des CAL

Une fois la fonctionnalité License Server activée, il est nécessaire de provisionner les licences d’accès (CAL) à proprement parler, afin qu’elles puissent être attribuées aux utilisateurs qui se connectent au bureau à distance.

  1. Récupérez le License Server Id depuis l’étape précédente ou à l’aide des commandes suivantes :
    $wmiClass = ([wmiclass]"\\localhost\root\cimv2:Win32_TSLicenseServer")
    $wmiClass.GetLicenseServerId().sLicenseServerId
    image

  1. Rendez-vous sur https://activate.microsoft.com/ et sélectionnez l’option Install client access licenses

  1. Indiquez le License Server Id, sélectionnez le type de licence correspondant à la commande et complétez le reste des informations :
    image

  1. Indiquez le nombre de licences correspondant à la commande, ainsi que l’agreement number du contrat :
    image

  1. Confirmez. Le service d’activation renvoie la clé d’activation des CAL
    image

  1. L’activation des CAL peut désormais être finalisée à l’aide des commandes suivantes (pensez à remplacer le License Pack Id par la valeur obtenue précédemment, en supprimant les tirets)

$licensePackId = 'Y3XXXXXXXXXXXXXXXX'

$wmiClass = ([wmiclass]"\\localhost\root\cimv2:Win32_TSLicenseKeyPack")

$wmiClass.InstallLicenseKeyPack($licensePackId) 

  1. En cas de réussite de l’opération, Return Value doit afficher 0 et KeyPackId contient l’Id du pack de licence ajouté (incrémenté de 1 à chaque ajout d’un nouveau pack sur le serveur).
    image


Et voilà, votre serveur RDS License Server est prêt!

SCOM - Analyser un managed module

Débutons par un bref rappel sémantique : dans SCOM, le terme workflow désigne l’ensemble des éléments qui constituent un moniteur, une règle, une découverte ou une tache. Un workflow peut donc être constitué d’une data source, d’une ou plusieurs probes, de condition detections, de writeactions…

La grande majorité des Management Packs contient des workflows consistant en un assemblage de modules préexistants, par exemple :

  • Une datasource basée sur le module “scheduler” pour déclencher le workflow à intervalle régulier
  • Une probe basée sur le module “powershell script” pour exécuter un script. C’est ce dernier qui ira effectuer des tests et en renverra le résultat dans le workflow
  • Une ou plusieurs condion detections basées sur le module “Filter”, afin de détecter si le résultat du script indique un problème ou non
  • Une writeaction pour déclencher l’alerte si le filtre indique un problème.

Ces modules préexistants sont la vraie fondation de tout workflow SCOM, et leur code est en général du C# contenu dans une dll : c’est ce que l’on appelle des Modules Natifs (“native modules”).

Certains Management Packs poussent ce principe encore plus loin, en intégrant des modules créés de toutes pièces et dédiés à leur utilisation au lieu d’intégrer les modules natifs dans leurs workflows : on les appelle des modules managés (managed modules).

Les raisons de ce choix sont le plus souvent :

  • L’ajout de fonctionnalités irréalisables autrement
  • La réutilisation de code compilé préexistant
  • L’optimisation des performances des workflows : en effet, un workflow powershell tel que décrit ci-dessus permet de découvrir l’immense majorité des cas rencontrés. Cependant, il nécessite une exécution du script complète à chaque fois : chargement de powershell, chargement des modules powershell, authentification sur l’application à interroger, collecte des données puis déconnexion, déchargement de powershell etc. Un managed module reste lui en mémoire indéfiniment et peut ainsi ne boucler que sur la partie “collecte des données”, ce qui allège grandement le traitement.

Malheureusement cette technique impacte aussi fortement la lisibilité du code et la possibilité de le débugger par n’importe qui, puisque tout ou partie du workflow est maintenant “caché” dans une dll.

Prenons un exemple concret : les dernières versions du Management Pack pour SQL Server intègrent des managed modules, et il arrive parfois que l’alerte suivante se déclenche : MSSQL on Windows: Database is in offline/recovery pending/suspect/emergency state.

Pourtant, le détail de l’alerte n’est pas toujours probant puisqu’il peut n’indiquer que les informations suivantes :

  • MonitoringStatus : Bad
  • DatabaseState : ONLINE
  • IsAccessible : false
  • IsMirroringMirror : false
  • IsAlwaysOnReplica : true
  • ErrorCode : 0
  • ErrorDescription : <vide>

Très insuffisant pour comprendre d’où vient le problème… Dans ce genre de cas, il est donc nécessaire d’aller voir comment fonctionne le moniteur d’où provient l’alerte pour tenter de comprendre son passage en échec. En remontant le fil du workflow, on constate que les tests à proprement parler sont réalisés dans une Probe nommée MSSQL on Windows: Database Status Probe Action, dont il n’est pas possible de voir le fonctionnement directement dans le code du Management Pack puisqu’elle repose sur un Managed Module :

image

Nous avons cependant deux informations utiles :

  • Assembly : c’est le nom de la dll qui contient le code compilé du module
  • Type : c’est le nom de la fonction qui produit les données utilisées par le moniteur.

La dll peut être récupérée sur n’importe quel serveur SQL disposant d’un agent SCOM dans le dossier Health Service State, ou en l’extrayant du fichier .mpb du management pack avec l’outil MPBUtil de Silect.

Elle peut ensuite être ouverte à l’aide de l’outil gratuit JetBrains dotPeek.

Une fois ouverte, on retrouve la fonction DBStatusMonitoring en suivant l’arborescence :

 image

Un clic-droit > go to implementation permet de retrouver le code exécuté par cette fonction à proprement parler et, en particulier, une zone que l’on interprète facilement comme étant l’exécution d’une requête SQL résultant en plusieurs champs qui correspondent complètement aux détails de notre alerte d’origine :

image

Cependant, la requête SQL a proprement parler n’est pas visible ici. Elle est en réalité contenue dans une “Ressource”  (un champ texte annexe de la dll) nommée GetDBStatuses :

image

Retournons donc dans l’explorateur de la dll pour ouvrir les ressources, dans laquelle nous allons rechercher GetDBStatuses. Et voilà notre requête SQL :

image


Vous pouvez désormais copier cette requête et l’analyser et l’exécuter manuellement pour comprendre d’où provient l’alerte!

D’autres modules managés auront un fonctionnement différent, mais le principe d’analyse restera identique… A vous de jouer!

SCOM - Comprendre la supervision des erreurs réseau

Un client m’a récemment demandé comment fonctionnait techniquement la supervision des erreurs sur les ports réseau, et il m’a fallu un peu d’analyse pour lui fournir une réponse complète.

Commençons par le début : cette supervision est assurée par les moniteurs High Output et High Input Error Rate.

La lecture du Knowledge associé fournit quelques informations, mais reste assez floue :

This monitor enters a warning state when the percentage of output packet errors is greater than the error threshold configured for this interface (the default is 10%). The output packet error percentage is derived by dividing the number of output packets in error by the total number of output packets. The result of this calculation is expressed as a percentage and compared to the error threshold.

On y apprend donc que le moniteur réagit à un pourcentage d’erreur supérieur à 10%, et que ce taux est calculé en divisant le nombre de paquets en erreur par le nombre de paquets total.

Très bien, mais d’où proviennent ces informations ? Pour le savoir, il va falloir comme souvent aller regarder ce qui se passe dans le code.

On y constate en premier lieu que ces moniteurs ne s’appuient non pas sur deux OIDs (« nombre total de paquets » et « paquets en erreur »), mais sur 6 OIDs pour le moniteur Input et 4 OIDs pour le moniteur Output :

<OIDifHCInUcastPkts>.1.3.6.1.2.1.31.1.1.1.7.$Target/Property[Type="NetworkLibrary!System.NetworkManagement.NetworkAdapter"]/Index$</OIDifHCInUcastPkts>

<OIDifHCInMulticastPkts>.1.3.6.1.2.1.31.1.1.1.8.$Target/Property[Type="NetworkLibrary!System.NetworkManagement.NetworkAdapter"]/Index$</OIDifHCInMulticastPkts>

<OIDifHCInBroadcastPkts>.1.3.6.1.2.1.31.1.1.1.9.$Target/Property[Type="NetworkLibrary!System.NetworkManagement.NetworkAdapter"]/Index$</OIDifHCInBroadcastPkts>

<OIDifInDiscards>.1.3.6.1.2.1.2.2.1.13.$Target/Property[Type="NetworkLibrary!System.NetworkManagement.NetworkAdapter"]/Index$</OIDifInDiscards>

<OIDifInErrors>.1.3.6.1.2.1.2.2.1.14.$Target/Property[Type="NetworkLibrary!System.NetworkManagement.NetworkAdapter"]/Index$</OIDifInErrors>

<OIDifInUnknownProtos>.1.3.6.1.2.1.2.2.1.15.$Target/Property[Type="NetworkLibrary!System.NetworkManagement.NetworkAdapter"]/Index$</OIDifInUnknownProtos>



Une rapide recherché dans une base de MIB nous indique que ces OIDs remontent les valeurs suivantes :

  • Nombre de paquets Unicast
  • Nombre de paquets Multicast
  • Nombre de paquets Broadcast
  • Nombre de paquets Discarded (input seulement)
  • Nombre de paquets en Erreur
  • Nombre de paquets d’un protocole inconnu (input seulement).

Et également qu’il s’agit du nombre total de paquets transmis depuis le démarrage de l’équipement réseau.

Comment SCOM fait-il donc pour agglomérer ces données et en ressortir un pourcentage ?

Voyons maintenant un peu plus loin dans le code, du côté du module ConditionDetection System.NetworkManagement.NetworkAdapter.InputErrorRate.ifMIB.

Il repose principalement sur une ConditionDetection de type Network.Computation, qui permet des expressions assez copieuses :

clip_image002

Nous nous intéresserons ici à trois éléments en particulier, les champs DeltaValue et les expressions BranchValueExpression et Summation/Division/Product

Delta Value

Ces éléments permettent de travailler non pas sur la valeur absolue renvoyée depuis l’OID, mais plutôt sur la valeur de son augmentation depuis la mesure précédente.
Autrement dit, si le moniteur vérifie toutes les 10 minutes la valeur de l’OID et que cette dernière est d’abord « 1000 » puis, 10 minutes plus tard, « 1500 », alors la valeur de l’expression DeltaValue sera la différence entre 1500 et 1000, soit 500.

Cela permet au moniteur de superviser une variation du taux d’erreur lors du dernier intervalle de mesure uniquement, et pas en se basant sur des valeurs « diluées » depuis le démarrage du device.

 BranchValueExpression
clip_image003

Probablement le champ qui m’a donné le plus de fil à retordre, il est composé de trois sous-blocs :

<SimpleExpression>

<TrueValueExpression>

<FalseValueExpression>



Son fonctionnement est en réalité assez simple : l’expression comprise dans SimpleExpression est évaluée en premier et, en fonction de si elle est Vraie ou Fausse, le workflow continue par l’évaluation de l’expression True ou False.

Ce mécanisme est utilisé ici pour déterminer une propriété du moniteur (IsMib2SNMP) et moduler le fonctionnement en conséquence puis plus loin pour éviter les « divisions par 0 ».

Sumation/Division/Product

Ces expressions sont celles qui vont réellement produire le résultat qui nous intéresse, le taux d’erreur sur le port réseau.

Dans un premier temps, les valeurs obtenues pour toutes les OIDs (Unicast/Multicast/Broadcast/Discard/Error/Unknown) vont être additionnées (Summation) pour obtenir le total de paquets transmis :
clip_image005

Puis le nombre de paquets en erreur va être divisé (Division) par le total obtenu juste avant, et enfin cette dernière valeur sera multipliée (Product) par 100 :

clip_image007

De cette facon, on a bien comme résultat final le pourcentage de paquets en erreur lors du dernier intervalle de mesure.

SCOM – Faire correctement échouer une tâche


Sous ce titre qui peut prêter à sourire se cache une fonctionnalité peu connue et finalement pas vraiment indispensable, mais qui donnera une touche un peu plus finie et professionnelles à vos management packs.

Lorsque vous exécutez une tâche basée sur un script Powershell, il peut arriver que ce dernier échoue à remplir son but pour diverses raisons. Dans ce cas, par défaut, la tâche présentera malgré tout un résultat en succès ou, au mieux, un résultat ressemblant au suivant :

clip_image002

Vous avez cependant déjà vu des tâches qui échouent « proprement », avec un symbole d’échec et le message d’erreur associé dans la sortie de la tâche.

Pour obtenir le même résultat dans vos propres développements, c’est très simple, il y a deux conditions à remplir :

- Lever une exception dans le script, via une commande Throw

- Ajouter le paramètre <StrictErrorHandling>true</StrictErrorHandling> à la WriteAction ou à la Probe utilisée dans votre tâche :
clip_image004

Et vous obtiendrez alors une sortie indiquant bien un Status Failed, avec l’icone « rouge » et le texte envoyé dans le Throw dans la sortie lorsque la tâche échoue :

clip_image006

SCOM 2019 – An Item With The Same Key Has Already Been Added après l’installation de l’UR1


SCOM 2019 UR1 a introduit de nouveaux management packs universels pour Linux. Ils vont permettre de simplifier la supervision des différentes distributions : plus besoin d’un MP différent pour chaque distribution et chaque version de cette distribution, les MP universels pourront gérer toutes les futures versions des distributions supportées.

Cependant, le déploiement de ces MP universels dans un environnement disposant encore du MP RHEL 6 et de serveurs découverts dans cette version provoque un petit souci : la vue Unix/Linux Computers ne se charge plus et l’erreur suivante apparait :

clip_image001

Par ailleurs, l’erreur suivante est visible dans le journal d’événements :

System.ArgumentException: An item with the same key has already been added.
   at System.ThrowHelper.ThrowArgumentException(ExceptionResource resource)
   at System.Collections.Generic.Dictionary`2.Insert(TKey key, TValue value, Boolean add)
   at Microsoft.SystemCenter.CrossPlatform.UI.OM.Integration.MonitoringObjectPathToMonitoringObjectDictionary..ctor(IEnumerable`1 monitoringObjects)
   at Microsoft.SystemCenter.CrossPlatform.UI.OM.Integration.UnixComputerOperatingSystemHelper.JoinCollections(IEnumerable`1 managementServers, IEnumerable`1 resourcePools, IEnumerable`1 unixcomputers, IEnumerable`1 operatingSystems)
   at Microsoft.SystemCenter.CrossPlatform.UI.OM.Integration.UnixComputerOperatingSystemHelper.GetUnixComputerOperatingSystemInstances(String criteria)
   at Microsoft.SystemCenter.CrossPlatform.UI.OM.Integration.Administration.UnixAgentQuery.DoQuery(String criteria)
   at Microsoft.EnterpriseManagement.Mom.Internal.UI.Cache.Query`1.DoQuery(String criteria, Nullable`1 lastModified)
   at Microsoft.EnterpriseManagement.Mom.Internal.UI.Cache.Query`1.FullUpdateQuery(CacheSession session, IndexTable& indexTable, Boolean forceUpdate, DateTime queryTime)
   at Microsoft.EnterpriseManagement.Mom.Internal.UI.Cache.Query`1.InternalSyncQuery(CacheSession session, IndexTable indexTable, UpdateReason reason, UpdateType updateType)
   at Microsoft.EnterpriseManagement.Mom.Internal.UI.Cache.Query`1.InternalQuery(CacheSession session, UpdateReason reason)
   at Microsoft.EnterpriseManagement.Mom.Internal.UI.Cache.Query`1.TryDoQuery(UpdateReason reason, CacheSession session)
   at Microsoft.EnterpriseManagement.Mom.Internal.UI.Console.ConsoleJobExceptionHandler.ExecuteJob(IComponent component, EventHandler`1 job, Object sender, ConsoleJobEventArgs args)

La raison en est simple : le nouveau MP universel a découvert une nouvelle fois vos serveurs RHEL 6, ce qui crée des doublons dans la base de données et un plantage de la console lorsqu’elle tente de les lister.

Vous disposez maintenant de deux solutions :

- Supprimer le MP RHEL 6 qui, par ailleurs, n’est plus supporté dans SCOM 2019. Vos serveurs RHEL6 seront toujours supervisés par le MP universel, mais ce dernier ne contient pas exactement les même moniteurs, il faudra donc vérifier qu’il répond à vos besoins. Par ailleurs, si vous aviez développé des règles et moniteurs qui ciblent spécifiquement la classe RHEL 6, il faudra les réécrire.
Par ailleurs, vous ne pourrez plus découvrir de nouveaux serveurs RHEL 6 si vous supprimez ce management pack.

- Désactiver la découverte des serveurs RHEL 6 via un override, puis exécuter la commande Remove-SCOMDisabledClassInstance.

Une fois débarrassé de ces doublons, tout devrait rentrer dans l’ordre !

SCOM – Création et peuplement dynamique de groupes à partir d’une clé de registre (le retour en mieux)

J’avais publié il y a quelques années un article montrant comment créer et peupler automatiquement des groupes à partir d’une clé de registre, à l’aide d’un script vbs : Création et peuplement dynamique de groupes à partir d’une clé de registre.

J’ai récemment rencontré un besoin similaire, et j’ai cette fois voulu expérimenter une technique différente et que je considère comme plus élégante, car elle ne se base que sur l’utilisation standard de deux modules natifs, sans faire appel au moindre bout de script.

Je ne reviendrai pas ici sur la nécessité de déclarer une classe unhosted, non singleton et avec un attribut clé pour le groupe : tout cela est détaillé dans l’article précédent.

Entrons donc directement dans le vif du sujet !

Comme je viens de le rappeler, un groupe est l’instance d’une classe. Les objets membres de ce groupe sont eux aussi des instances de différentes classe et ils sont rattachés au groupe à l’aide d’une relation de containment.

Notre objectif est donc de créer des instances de la classe du groupe ainsi que des relations de containment entre ces instances et les objets qui vont venir peupler les groupes.

Et pour ce faire, sans utiliser aucun script, il existe un module parfaitement adapté : System.Discovery.FilteredClassAndRelationshipSnapshotDataMapper.

clip_image002

Ce module va tout simplement créer une instance de la classe que vous lui indiquerez, avec les propriétés que vous lui indiquerez ; ainsi qu’une instance de la relation de votre choix, entre les instances indiquées.

Bien entendu, il n’est pas question ici de remplir les champs de manière statique : ce module sera intégré dans votre workflow de découverte et récupérera donc toutes les informations dont il a besoin depuis les modules précédents.

Dans cet exemple, nous souhaitons peupler les groupes à partir d’une clé de registre : nous devrions donc créer notre datasourcemoduletype avec le très classique module Microsoft.Windows.Discovery.RegistryProvider comme datasource, puisque son rôle est justement d’aller lire dans la base de registre.

Mais il existe une solution encore plus simple : un module natif combinant RegistryProvider et SnapshotDataMapper existe déjà ! Il s’agit du module Microsoft.Windows.FilteredRegistryClassAndRelationshipDiscoveryProvider.

Une fois ces éléments mis bout à bout, on arrive au fragment suivant qu’il suffit de modifier en y indiquant la clé de registre qui vous intéresse :

<ManagementPackFragment SchemaVersion="2.0" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  <TypeDefinitions>
    <EntityTypes>
      <ClassTypes>
        <ClassType ID="Test.My.Computers.Group" Accessibility="Public" Abstract="false" Base="MSIL!Microsoft.SystemCenter.InstanceGroup" Hosted="false" Singleton="false" Extension="false" >
          <Property ID="RegistryValue" Type="string" AutoIncrement="false" Key="true" CaseSensitive="false" MaxLength="256" MinLength="0" Required="false" Scale="0" />
        </ClassType>
        
      </ClassTypes>
    </EntityTypes>
  </TypeDefinitions>
  <Monitoring>
    <Discoveries>
      <Discovery ID="Test.My.Computers.Group.Discovery" Enabled="true" Target="Windows!Microsoft.Windows.OperatingSystem" ConfirmDelivery="false" Remotable="true" Priority="Normal">
        <Category>Discovery</Category>
        <DiscoveryTypes>
          <DiscoveryClass TypeID="Test.My.Computers.Group">
            <Property TypeID="Test.My.Computers.Group" PropertyID="RegistryValue" />
          </DiscoveryClass>
        </DiscoveryTypes>
        <DataSource ID="DS" TypeID="Windows!Microsoft.Windows.FilteredRegistryClassAndRelationshipDiscoveryProvider">
          <ComputerName>$Target/Host/Property[Type="Windows!Microsoft.Windows.Computer"]/PrincipalName$</ComputerName>
          <RegistryAttributeDefinitions>
            <RegistryAttributeDefinition>
              <AttributeName>RegistryValueExists</AttributeName>
              <Path>SOFTWARE\Test\RegistryValue</Path>
              <PathType>1</PathType>
              <!-- 0=regKey 1=regValue -->
              <AttributeType>0</AttributeType>
              <!-- 0=CheckIfExists (Boolean) 1=treat data as (String) 2=treat data as (Integer) -->
            </RegistryAttributeDefinition>
            <RegistryAttributeDefinition>
              <AttributeName>RegistryValue</AttributeName>
              <Path>SOFTWARE\Test\RegistryValue</Path>
              <PathType>1</PathType>
              <!-- 0=regKey 1=regValue -->
              <AttributeType>1</AttributeType>
              <!-- 0=CheckIfExists (Boolean) 1=treat data as (String) 2=treat data as (Integer) -->
            </RegistryAttributeDefinition>
          </RegistryAttributeDefinitions>
          <Frequency>14400</Frequency>
          <ClassId>$MPElement[Name="Test.My.Computers.Group"]$</ClassId>
          <ClassInstanceSettings>
            <Settings>
              <Setting>
                <Name>$MPElement[Name='System!System.Entity']/DisplayName$</Name>
                <Value>Test My Group - $Data/Values/RegistryValue$</Value>
              </Setting>
              <Setting>
                <Name>$MPElement[Name='Test.My.Computers.Group']/RegistryValue$</Name>
                <Value>$Data/Values/RegistryValue$</Value>
              </Setting>
            </Settings>
          </ClassInstanceSettings>
          <RelationshipId>$MPElement[Name="MSIL!Microsoft.SystemCenter.InstanceGroupContainsEntities"]$</RelationshipId>
          <SourceTypeId>$MPElement[Name="Test.My.Computers.Group"]$</SourceTypeId>
          <SourceRoleSettings>
            <Settings>
              <Setting>
                <Name>$MPElement[Name='Test.My.Computers.Group']/RegistryValue$</Name>
                <Value>$Data/Values/RegistryValue$</Value>
              </Setting>
            </Settings>
          </SourceRoleSettings>
          <TargetTypeId>$MPElement[Name="Windows!Microsoft.Windows.Computer"]$</TargetTypeId>
          <TargetRoleSettings>
            <Settings>
              <Setting>
                <Name>$MPElement[Name='Windows!Microsoft.Windows.Computer']/PrincipalName$</Name>
                <Value>$Target/Host/Property[Type="Windows!Microsoft.Windows.Computer"]/PrincipalName$</Value>
              </Setting>
            </Settings>
          </TargetRoleSettings>
          <Expression>
            <SimpleExpression>
              <ValueExpression>
                <XPathQuery Type="Boolean">Values/RegistryValueExists</XPathQuery>  
              </ValueExpression>
              <Operator>Equal</Operator> 
              <ValueExpression>
                <Value Type="Boolean">true</Value> 
              </ValueExpression>
            </SimpleExpression>
          </Expression>
        </DataSource>
      </Discovery>
    </Discoveries>
  </Monitoring>
  <LanguagePacks>
    <LanguagePack ID="ENU" IsDefault="true">
      <DisplayStrings>
        <DisplayString ElementID="Test.My.Computers.Group">
          <Name>Test Computers Group</Name>
        </DisplayString>
        <DisplayString ElementID="Test.My.Computers.Group.Discovery">
          <Name>Test Computers Group Discovery</Name>
          <Description>This discovery rule creates and populates groups of Windows Computer Objects that contain a registry key, based on this key's value</Description>
        </DisplayString>
      </DisplayStrings>
    </LanguagePack>
  </LanguagePacks>
</ManagementPackFragment>

 

SquaredUp - Récupérer un dashboard bloqué

Lorsque l’on s’aventure dans la modification manuelle du code JSON d’un dashboard, une mauvaise manipulation est vite arrivée et peut résulter dans le blocage complet du dashboard : il n’est plus possible de le modifier, de le sauvegarder ou de revenir en arrière ; tous les boutons sont inopérants.

Heureusement, il est possible de se sortir de ce mauvais pas sans devoir totalement supprimer le dashboard !

En effet, lorsqu’ils sont en cours de modification, les dashboards sont stockés temporairement sur le serveur qui héberge SquaredUp dans le dossier C:\inetpub\wwwroot\SquaredUpv4\User\Packages\VotreLogin\dashboards sous forme de fichier JSON que vous pouvez ouvrir et modifier avec n’importe quel éditeur de texte.

Il vous suffit donc de corriger votre erreur, enregistrer le fichier, rafraichir le dashboard dans la console SquaredUp et le tour est joué, vous avez récupéré la main !

 

 

SquaredUp - Matrix Tiles et valeur absente

 Suite à mon précédent article d’introduction à SquaredUp, il est maintenant temps de s’intéresser à quelques-uns des problèmes rencontrés lors de la construction de quelques dashboards ; en particulier ici lors de l’utilisation de tuiles de type « Matrix ».

Les tuiles « Matrix » sont très intéressantes car elles permettent d’afficher plusieurs informations sous différents formats (état de l’objet, état d’un moniteur, sparkline de performance, SLA propriété de l’objet…) sur une même ligne :

Certaines subtilités ne sont cependant pas documentées, et on peut rapidement se casser les dents sur une opération qui semblait pourtant simple au premier abord.

Propriété de l’objet

 

Il est possible d’afficher une colonne contenant simplement une propriété de l’objet au format texte. Dans la capture ci-dessus, on affiche par exemple la propriété DomainDnsName de la classe Microsoft.Windows.Computer.

La syntaxe est très simple (exemple fourni dans la documentation de SquaredUp) :

{
    "title": "Domain DNS",
    "_type": "celltile/text",
    "config": {
        "display": {
            "contentTemplate": "{{properties.domainDnsName}}"
        }
    }
}


Ce que n’indique pas clairement la documentation, c’est l’obligation de respecter strictement la casse du nom de la propriété… à l’exception de son premier caractère, qui doit toujours être écrit en minuscule.

L’exemple le montre bien, mais sans explication écrite c’est loin d‘être évident !

 

Valeur calculée

SquaredUp utilise la syntaxe Mustache et supporte donc presque toutes les fonctions de transformation issues de Javascript.

Il est donc en théorie possible de ne garder que deux décimales après la virgule lors de l’affichage d’un compteur de performance avec la fonction ToFixed :

"labelTemplate": "{{ (value).ToFixed(2) }}"

 

Malheureusement, cela résulte en une colonne vide.

Et la raison est identique au point précédent : il est impératif d’utiliser la casse exacte de la syntaxe Javascript… sauf le premier caractère qui doit obligatoirement être en minuscule.

La syntaxte suivante fonctionne donc :

"labelTemplate": "{{ (value).toFixed(2) }}"

 

Simple à corriger… mais rageant lorsque l’on bute sur le problème !

 

Valeur d’un objet hébergée dans une colonne « bar »

L’exemple donné par la documentation semble encore une fois d’une simplicité enfantine :

 

{
    "title": "Memory Usage Bar",
    "_type": "celltile/bar",
    "config": {
        "source": {
            "objectname": "Memory",
            "countername": "PercentMemoryUsed"
        },
        "display": {
            "valueTemplate": "{{(value ? Math.floor(value) : 0)}}"
        }
    }
}

Le nom du compteur, éventuellement un peu de formatage de la valeur via une fonction Javascript et voilà.

Mais lorsque la Matrix est ciblée sur une classe hébergée par une autre, cela ne fonctionne pas : la colonne est vide.

Un peu plus loin dans la documentation, dans la section concernant les sparklines, on trouve l’information suivante : lorsque  la tuile est scopée sur un objet hébergé tel qu’un disque, une base de données ou un site web, il faut modifier la configuration du groupement :

"transforms": [
    {
        "operator": "group",
        "parameters": {
            "keys": [
                "managedEntityId"
            ]
        }
    },
    {
        "operator": "merge",
        "parameters": {
            "sourceKey": "id",
            "targetKey": "key.managedEntityId"
        }
    }
]

 

Il semble alors naturel de tenter la même modification pour la sparkline. Malheureusement sans succès…

La solution, qui m’a été apportée par le support de SquaredUp, est la suivante :

"transforms": [
    {
        "operator": "merge",
        "parameters": {
            "sourceKey": "id",
            "targetKey": "managedEntityId"
        }
    }
]



Il s’agit quasiment de la même syntaxe, mais en n’utilisant que le merge.

C’est tout pour aujourd’hui !