PI Services

Le blog des collaborateurs de PI Services

Sécurité : Local Admin Password Solution (LAPS) - Partie 4

Réinitialisation du mot de passe :

Avec un compte faisant partie de "Read_Laps"

Normalement lorsqu'un compte ne fait pas partie du groupe "Reset_Laps", il ne bénéficie pas du droit de réinitialiser le mot de passe, vérifions cela.

Avec le client lourd  :

En effet nous pouvons constater que lorsque l'on essaie de réinitialiser le mot de passe le client affiche au bas de la fenêtre un message "Failed to request password reset"

Avec Powershell :

Avec Powershell le résultat est le même (voir plus explicite), l'utilisateur ne possède pas les droits.

Avec un compte faisant partie de "Reset_Laps"

 

Maintenant nous allons utiliser un compte qui se trouve dans le groupe "Reset_Laps" mais pas dans le groupe "Read_Laps", par conséquent il n'est pas possible de lire le mot de passe mais je devrais pouvoir le réinitialiser. 

Avec le client lourd  :

Cette fois-ci nous pouvons constater que le mot de passe n'est pas lisible, et au bas du client lourd le message "Password reset request was successful".

Avec Powershell :

Conclusion :

Grâce à cette solution gratuite de Microsoft vous pouvez améliorer la sécurité de votre parc informatique en réduisant la portée d'attaque d'une intrusion à l'aide du compte administrateur local.

 

https://www.microsoft.com/en-us/download/details.aspx?id=46899&wt.mc_id=fy16techdays

https://technet.microsoft.com/en-us/library/security/3062591?wt.mc_id=fy16techdays

https://blogs.msdn.microsoft.com/laps/

https://support.microsoft.com/en-us/kb/3062591

[Powershell] Héritage des GPO bloqué

 

Voici quelques commandes Powershell pour trouver les OU dont l'héritage est bloqué:

$GPOBlocked = Get-ADOrganizationalUnit -SearchBase "DC=LAB,DC=Org" -Filter * | Get-GPInheritance | Where-Object {$_.GpoInheritanceBlocked}
$GPOBlocked.count
$GPOBlocked | Select Name,Path

Le DistinguishedName (Path) n'est pas toujours agréable à lire je fais donc une conversion en CanonicalName, ensuite vous pouvez afficher les données à l'écran ou les écrire dans un fichier:

$GPOBlocked | ForEach-Object {
        $Name = $_."Name"
        $Path = $_."Path"
        $CanonicalName = Get-ADOrganizationalUnit -Filter {DistinguishedName -like $Path} -Properties CanonicalName | Select-Object -ExpandProperty CanonicalName
        Write-Output "$Name; $CanonicalName" | Add-Content C:\Temp\GpoInheritanceBlocked.txt
    }

 

 

[Powershell] Excel ne s'enregistre pas via le Planificateur de tâches

Lancer un script Powershell via le planificateur de tâches ne pose aucun problème, en revanche si ce dernier souhaite utiliser Excel (par l'intermédiaire de la commande "new-object -comobject excel.application") on peut rencontrer un problème.

Je dis "on peut" car cela fonctionne si l'on a pas coché la case "Run Whether user is logged on or not", en revanche si cette dernière est cochée, Excel ne sera pas en mesure d'enregistrer le fichier.

Pourquoi ?

Par défaut l'application Excel s'exécute avec le compte "Utilisateur exécutant (The Launching User)", mais si j'utilise une tâche planifiée qui ne nécessite pas que je sois connecté Excel ne parvient pas a déterminer qui fait appel à lui.

Que Faire ?

Simplement en déclarant qui  exécute l'application Excel via la MMC -> Services des composants.

Je vous renvoie au point 2 (Gestion de l'Excel distant) de mon précédent article : [Powershell] Ecrire dans un Excel distant.

Sécurité : Local Admin Password Solution (LAPS) - Partie 3

Vérifications Serveur / Clients

Depuis le serveur

Vérifions l'état du mot de passe du compte Administrateur Local d'une machine cible.

Avec le client lourd  :

Avec Powershell :

Depuis l'Active Directory dans l'onglet éditeur d'attribut 

Depuis le poste Client

Avec un compte faisant partie de "Read_Laps"

Réalisons donc une mise à jour de la stratégie de groupe 

Nous allons pouvoir vérifier l'état du mot de passe du compte admin local.

 

Avec le client lourd  :

Avec Powershell :

Avec un compte faisant partie de "Reset_Laps"

Avec le client lourd  :

Avec Powershell :

 

Test de connexion

Nous testerons donc de nous connecter à la machine avec le nouveau mot de passe

C'est fonctionnel.

Sécurité : Local Admin Password Solution (LAPS) - Partie 2

Mise en Oeuvre :

Installation sur l'infrastructure :

Pour réaliser l'installation de LAPS sur un serveur membre nous aurons besoin dans un premier temps d'un compte avec les droit Administrateur Local sur le serveur, puis de bénéficier des droits Admin du Schéma puis Admin du domaine ou Admin de ou des Unités d'Organisation en fonction de l'étendue sur laquelle vous souhaiter déployer LAPS. (Pour ma part je l'ai fait avec un compte "Admin du Domaine" sur lequel j'ai accordé les droits "Admin du schéma" le temps de l'installation).

L'installation se fait en suivant les étapes ci-dessous :

  1. Installation de LAPS avec les composants Serveur.
  2. Extension du schéma.
  3. Attribution des droits.
    1. Droits pour les computers .
    2. Création des groupes de sécurité.
    3. Délégation des droits aux groupes de sécurité sur les OU.
  4. Paramétrage des GPO
    1. Import des fichiers ADMX et ADML.
    2. Création et paramétrage de GPO.

Etape 1 : Installation de LAPS avec les composant serveur :

Le fichier msi fournit par LAPS permet d'installer les composants clients et serveurs, par défaut seul les composants clients sont installés, dans notre cas nous souhaitons déployer les composant serveurs nous sélectionnerons donc les "Management Tools".

Vous remarquerez la présence de 3 modules pour les "Management Tools" :

  1. Fat Client UI : Soit l'interface utilisateur client lourd permettant de récupérer les mot de passe en clair dans l'AD
  2. Powershell module : L'interface Powershell
  3. GPO Editor Templates : Les ADMX

Etape 2 : Extension du schéma

Exécuter Powershell en tant qu'Administrateur.

 

# Import du module
Import-module AdmPwd.PS
# Update du schéma
Update-AdmPwdADSchema

Etape 3 : Attribution des droits.

Pour attribuer aux machines le droit de modifier leurs attributs exécutez :

Set-AdmPwdComputerSelfPermission -OrgUnit "OU=Machines,DC=LAB,DC=INFO"

Pour créer les Groupes de sécurité afin de déléguer les droits (Lecture et Réinitialisation), vous avez deux options (en graphique via ADAC ou User & computers AD, ou avec Powershell, pour ma part ce sera Powershell).

 

# Création du Groupe Read_Laps
New-AdGroup -Name "Read_Laps" -SamAccountName "Read_Laps" -Description "Group For Read Permission" -GroupCategory Security -GroupScope Global -Path "OU=Groups,OU=IT,OU=Main,DC=LAB,DC=ORG"

# Création du Groupe Reset_Laps
New-AdGroup -Name "Read_Laps" -SamAccountName "Reset_Laps" -Description "Group For Reset Permission" -GroupCategory Security -GroupScope Global -Path "OU=Groups,OU=IT,OU=Main,DC=LAB,DC=ORG"

Nb: Le nom des groupes est un choix personnel, libre à vous de mettre autres choses.

Nous allons maintenant déléguer les droits sur une OU à l'aide des commandes suivantes :

 

# Permet d'attribuer le droit de lecture du mot de passe au Groupe "Read_Laps" sur l'OU "Main/IT/Computers"
Set-AdmPwdReadPasswordPermission -Identity "OU=COMPUTERS,OU=Main,DC=LAB,DC=ORG" -AllowedPrincipals Read_Laps –Verbose

# Permet d'attribuer le droit de changement du mot de passe au Groupe "Reset_Laps" sur l'OU "Main/IT/Computers"
Set-AdmPwdResetPasswordPermission –Identity "OU=COMPUTERS,OU=IT,OU=Main,DC=LAB,DC=ORG" –AllowedPrincipals Reset_Laps –Verbose

Nb : Le droit peut être déléguer sur l'ensemble du domaine, pour ma part seul les "Domain Admins" bénéficie de ce privilège.

Etape 4 : Paramétrage des GPO.

Par défaut, LAPS positionne les fichiers ADMX et ADML sur le poste local, si on veut les placer dans le SYSVOL de l’organisation, on ira donc récupérer :

  • C:\Windows\PolicyDefinitions\AdmPwd.admx -> A copier dans le "%systemroot%\sysvol\domain\policies\PolicyDefinitions"
  • C:\Windows\PolicyDefinitions\en-us\AdmPwd.adml -> A copier dans le "%systemroot%\sysvol\domain\policies\PolicyDefinitions\EN-US" (Option à répéter pour chaque langage de votre AD).

Nb: Si toutefois vous n'aviez pas les répertoires ci-dessus, il faudra les créer manuellement avec un compte ayant les droits.

Il ne nous reste plus qu'à déployer les stratégies de groupe pour définir le comportement de LAPS.

  1. On ouvre donc "Group Policy Management" et on créé une nouvelle GPO (Dans cet Démo, on la nommera simplement "LAPS").
  2. On accède aux nouveaux paramètres : Computer Configuration -> Policies -> Administrative Templates -> LAPS

C'est donc à partir de ces nouveaux paramètres que nous allons pouvoir activer ou inhiber LAPS et pouvoir agir sur les différentes politiques de sécurité :

  • Intervalle de changement de mot de passe.
  • Longueur et complexité du mot de passe.
  • Définir le nom du compte Administrateur (Seulement si ce dernier à été renommé).

Activation de LAPS

Paramètre du nom de compte Administrateur : Ne pas configurer si le compte est celui par défaut.

Spécifications de longueur et complexité du mot de passe.

La configuration serveur est maintenant terminée.

Installation sur le client :

Comme par défaut le fichier msi n'installe que les composants client, il vous sera possible de scripter l'installation à l'aide de le commande : msiexec /i <emplacement>\LAPS.x64.msi /quiet

La partie client de LAPS est un CSE (Client Side Extension), soit une extension qui vient s’enregister auprès du service client de stratégies de groupe de Windows. Il s'agit donc bien d'une installation d'un composant de l'OS et non un nouveau service, que vous pourrez retrouver par défaut sous %programfiles%\LAPS\CSE\AdmPwd.dll

A chaque déclenchement du CSE, ce dernier :

  • vérifie si le mot de passe est expiré à l'aide du dernier changement stocké dans l'attribut ms-Mcs-AdmPwdExpirationTime.
  • S'il l'est, en génère un de manière aléatoire basé sur les politiques de sécurité définies par la GPO.
  • Met à jour le mot de passe et l'inscrit en clair dans l'attribut ms-Mcs-AdmPwd et modifie l'attribut ms-Mcs-AdmPwdExpirationTime de l'objet Ordinateur dans l'Active Directory.

Voila la configuration est maintenant terminée, mappons la GPO et vérifions.

 

Sécurité : Local Admin Password Solution (LAPS) - Partie 1

Vous êtes vous déjà posé la question "Mon parc informatique est il vulnérable?".

En terme de sécurité dans une grande majorité des cas la réponse est "Oui", mais alors pourquoi?

Grand nombre de personnes justifierons que le parc est à jour (Update, antivirus, firewall...), à cela je ne vous poserais que deux questions :

  1. Le compte Admin Local est il désactivé sur toutes vos machines comme préconisé par Microsoft ? Si la réponse est non on peut déjà douter.
  2. Le mot de passe du compte Admin Local des postes et/ou des serveurs est il différent sur chaque machine ? Si la réponse est non on à une faille de sécurité.

Envisageons simplement que  le mot de passe sur une de vos machines vient à être compromis, d'un coup, c'est l'ensemble de votre parc qui l'est et cela sans avoir besoin d'utiliser le compte Admin du domaine.

Une fois la première machine compromise il n'y a plus qu'a rebondir sur les autres pour pouvoir compromettre l'intégralité du parc.

Et si je vous disais qu'on pouvait gérer ce problème avec un outil développé par "Microsoft Consulting Services" et fournit gratuitement par Microsoft, laissez moi vous présenter LAPS.

Introduction à LAPS :

LAPS, qu'est ce que c'est?

LAPS pour "Local Admin Password Solution" est un outils permettant la gestion et le stockage du mot de passe Administrateur Local des machines du domaine : Postes clients et Serveurs membres.

Cette solution permet:

  • De mettre à jour le mot de passe dans le contexte système et de l'envoyer au contrôleur de domaine à l'aide d'un composant client. Création aléatoire du mot de passe et transmission au contrôleur de domaine de manière crypté à l'aide de kerberos version 5 et Advanced Encryption Standard (AES).
  • De stocker le mot de passe et sa durée de validité comme attribut de l'objet machine de l'Active Directory. ms-Mcs-AdmPwd et ms-Mcs-AdmPwdExpirationTime.
  • Gérer le déploiement des paramètres de manière centraliser via les Stratégie de Groupes. Nom du compte, fréquence de changement, longueur et complexité.
  • La récupération des mots de passe pour les utilisateurs habilités via une interface graphique ou Powershell. Par défaut seuls les "Admins du domaine" sont autorisés à accéder à ces informations mais il est possible de déléguer les droits sur un groupe.

Illustration à l'aide d'un schéma d'architecture de la solution :

Jusque là c'est sympa mais comment ça fonctionne?

Le coeur  de la solution LAPS est une extension d'un composant du client Stratégie de Groupe (CSE - Client Side Extension) qui pendant la mise à jour des Objets de Stratégies de Groupe, effectue et peut forcer les actions suivantes  :

  1. Vérifier si le mot de passe Administrateur Local est expiré (Contrôle de la date de validité et de la configuration de la GPO).
  2. Générer un nouveau mot de passe si l'ancien a expiré ou nécessite d'être changé avant expiration.
  3. Valider que le nouveau mot de passe répond aux politiques de sécurité.
  4. Envoyer le nouveau mot de passe à Active Directory et le stocker avec un attribut confidentiel dans l'objet ordinateur dans l'Active Directory.
  5. Envoyer la prochaine date d'expiration du mot de passe à Active Directory et le stocker en tant qu'attribut dans l'objet ordinateur dans l'Active Directory.
  6. Modifie le mot de passe du compte Administrateur local.

Il est possible de déléguer les droits suivants:

  • Read Permission : Permet de lire le mot de passe depuis l'Active Directory, le client lourd LAPS ou avec Powershell par les utilisateurs autorisés. L'autorisation se fait par délégation de droits à des Groupes de sécurités (par défaut les "Admins du domaine").
  • Reset Permission : Permet de soumettre une demande de changement de mot de passe depuis le client lourd LAPS ou avec Powershell.

Prérequis et OS supportés :

Prérequis :

Infrastructure :

  • Active Directory : Windows Server 2003 SP1 ou ultérieur (L'attribut confidentiel n'est supporté qu'à partir du 2003 SP1 dans Active Directory)
  • Ajout des attributs ms-Mcs-AdmPwd et ms-Mcs-AdmPwdExpirationTime (Action réalisé lors de l'installation).
  • Restriction aux utilisateurs qui ne doivent pas accéder à l'attribut (retrait du All Extended Rights pour les groupes sensibles).
  • Installation & paramétrage d'un ADMX.
  •  Être Admin du Schéma le temps de l'installation (une Extension de Schéma est requise lors de l'installation)

Outils d'administrations :

  • .NET Framework 4.0.
  • Windows PowerShell 2.0 or ultérieur.

OS Supportés :

Clients : Windows 10 , Windows 8.1, Windows 8, Windows 7, Windows Vista avec le dernier Service Pack

Serveurs : Windows Server 2016, Windows Server 2012 R2, Windows Server 2012, Windows Server 2008 R2, Windows Server 2008,  Windows Server 2003 avec le Service Pack 2 (mais plus supporté)

Note : Les machines Itanium ne sont pas supportées.

 

Gestion de WSUS en Powershell - Partie 5

Comment Supprimer des KB dans son serveurs WSUS ?

Ce qui me déplait le plus dans un Serveur WSUS c'est de voir les KB dont je n'ai pas besoin (par Exemple les mises à jour pour Serveurs Itanium).

Toutes les mises à jours non désirées, remplacées ou expirées peuvent être déclinées, mais elles restent présentes en terme d'affichage.

Voyons comment les supprimer définitivement de la "WID", sans qu'elles ne reviennent après une synchronisation avec Windows Update.

Pour ce faire il faut donc:

  1. Définir les KB que l'on souhaite supprimer (on va créer une variable).
  2. Les supprimer à l'aide de Powershell.

1. Créer sa variable

Dans mon exemple je vais supprimer les KB déclinées et les Itanium.

$KB = $Wsus.GetUpdates()
$KBToDelete = $KB | Where-Object {($_.IsDeclined -eq $True) -or ($_.LegacyName -like "*Itanium*")}

2. Supprimer les KB

Maintenant supprimons les mises à jours associées à la variable.

# Check
$KBToDelete.count

# Action
$KBToDelete | Foreach-object {
  $Id = $_."Id"
  $KnowledgebaseArticles = $_."KnowledgebaseArticles"
  Write-Output "$KnowledgebaseArticles" | Add-Content C:\Temp\KBDeleteFromWid.txt
   $Wsus.DeleteUpdate($Id.UpdateId.ToString())
   }

# New Check
$KB = $Wsus.GetUpdates()
$KBToDelete = $KB | Where-Object {($_.IsDeclined -eq $True) -or ($_.LegacyName -like "*Itanium*")}
$KBToDelete.count

Et voila, comme ça je garde une vue relativement "propre" dans mes WSUS.

Si jamais vous souhaitiez de nouveau obtenir un KB supprimée, il faudra faire un import manuel depuis le serveur WSUS.

Import Manuel de mises à jour

  1. Sur le serveur WSUS développez "Update Services".
  2. Sur "Updates" faites un clic droit "Import Updates...".
  3. Vous serez redirigé vers la pages du Catalogue Microsoft®Update.
  4. Sur le Catalog Update, il faudra rechercher le numéro de KB que vous souhaitez importer.
  5. Sélectionnez votre KB, faites « ajouter » (répétez l'opération de recherche et d'ajout au Panier autant de fois que de KB à importer), puis aller dans votre panier.
  6. Cochez "Importer directement dans Windows Server Update Services" et faites « importer ».

[Powershell] Ecrire dans un Excel distant.

Exécuter Excel sur un poste Distant.

Quel est l'interêt ?

En dehors du fait qu'installer Excel sur un serveur n'est pas forcément une bonne pratique, dans mon cas cela me permet d'automatiser à 100% mes scripts Powershell, ces derniers fournissent des rapports détaillés à mes interlocuteurs, sans que je n'ai besoin de faire quelques modifications que ce soit (contrairement à un CSV).
Ainsi les scripts tournent de nuit et permettent à chacun de trouver son rapport le matin en arrivant, et ce même pendant mes vacances.

Comment ça marche

Pour exécuter Excel sur un poste distant via Powershell il faudra cumuler 2 paramètres.

  1. Activer CredSSP pour la délégation des identifiants.
  2. Autoriser le compte d'exécution à se servir de l'application Excel à Distance.

Voyons comment réaliser cela.

1- Activer CredSSP

J'ai déjà traité le sujet ICI.

2- Gestion Excel Distant

Attention la modification que nous allons réaliser n'est pas sans conséquence, comme nous pourrons le voir après, l'application Excel devient vraiment limitée pour tout autre utilisateur que celui qui sera déclaré.

Pour pouvoir autoriser le compte d'exécution à se servir de l'application Excel à distance, nous aurons besoin de la MMC.

Sur la machine qui traitera le fichier Excel :

  • Ouvrir la MMC > Ajouter ou supprimer des composants logiciels enfichables > Services de composants.

  • Développer Services de composants > Ordinateurs > Poste de travail > Configuration DCOM

  • Sur "Microsoft Excel Application" faites clic droit "propriétés", dans la fenêtre sélectionnez l'onglet "Identité"

  • Cochez la case "Cet utilisateur" puis faites "parcourir", sélectionnez "Tout l'annuaire" et enfin recherchez le compte d'exécution du script.

  • Entrez le mot de passe de votre compte d'exécution et faites "Appliquer".

Maintenant que le paramètre est fixé seul le compte définit est "autorisé" à se servir de l'application Microsoft Excel, l'expérience pour tout autre utilisateur est vraiment dégradé (pas d'impression, de sauvegarde, message d'erreur...); par conséquent si vous souhaitez pouvoir continuer à utiliser Excel, je vous invite à repasser sur "L'utilisateur exécutant" et n'activer la fonction que lors de l'exécution du script (pour ma part je ne l'active sur mon poste que la nuit).

Exemple de message d'erreur.

 

Annexe

En annexe un petit article sur la gestion d'Excel en Powershell :

https://blog.piservices.fr/post/2013/03/27/Powershell-Creation-de28099objets-personnalises

 

Windows Server : Forcer la détection du type de réseau sur une NIC

Bonjour,

Aujourd'hui nous allons voir comment forcer la détection du type de réseau afin que le bon profil de pare-feu (public, privé, réseau de domaine) soit appliqué sur une connexion réseau.

La manipulation se fait via une console Powershell lancée en tant qu'administrateur.

La 1ère étape est de récupérer le numéro d'index de l'interface que l'on veut modifier avec la commande suivante :

Get-NetConnectionProfile

Ce qui doit donner le résultat suivant :

Ensuite, il suffit d'appliquer la commande suivante afin de forcer le profil voulu :

Set-NetConnectionProfile -InterfaceIndex 12 -NetworkCategory Private

A noter que vous avez le choix parmi deux des trois profils suivants :

  • Private : correspond au profil Privé, celui qui est activé par défaut si vous acceptez l'activation de la découverte réseau lors de la première initialisation de l'interface
  • Public : correspond au profil Publique, celui qui est activé par défaut si vous refusez l'activation de la découverte réseau lors de la première initialisation de l'interface
  • DomainAuthenticated : correspond au profil Réseau de domaine. Il n'est pas forçable car il est automatiquement détecté lorsqu'une machine est membre d'un domaine et que celle-ci dispose d'une connectivité vers un contrôleur de domaine. Si votre machine est membre d'un domaine mais que le profil appliqué est privé ou public, c'est que vous avez très probablement un problème de connectivité avec le domaine AD dont la machine est membre.

 

 

 

Supervision – SQL – 3 scripts pour la supervision de Always On

 

Dans le cadre d’un portage de règle de supervision, les trois scripts ci-dessous ont été crées pour remonter l’état de:

- Un Availability Group

- Un Availability Replica

- Un Database Replica

 

En pratique ils utilisent a peu près la même requête SQL. Ils contiennent des éléments propre a l’api scom mais peuvent bien sur être adapté pour être utilisé indépendamment.

Ci-dessous les 3 scripts et l’exemple du code du premier, Check_SQL_AO_AvailabilityGroupStatus_Query_Version.ps1.

 

 

#######################################################################################
#         
#
         
#          Script: SQLAlwaysOnAvailGroupStatus.ps1

#          Purpose: Shows AlwaysOn Availability Group Status
#         
#          Parameters:

#           $DBServer: ShortName of DB Server
#          $InstanceFullName: Name of SQL Instance
#          $AvailGroupName: Availability Group Name
#         
#

########################################################################################

param ( 
           
$Arguments,

           
[string]$DBServer,
           
[string]$InstanceFullName,
       
[string]$AvailGroupName
           
                          
           
)


# Create local variables from override value
.([Scriptblock]::Create($Arguments))

$Scriptname = "SQLAlwaysOnAvailabilityReplicaStatus.ps1"


# Name of Instance

$ServerInstance = $InstanceFullName.Split('\')[1]



    #Determine TcpPort Used for Instance
    try
    {
    $TcpPort = Get-ItemProperty "HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\MSSQL11.$ServerInstance\MSSQLServer\SuperSocketNetLib\Tcp\IpAll" | select TcpPort -exp tcpport
    }
    catch
    {
    $Message = "Error during Retrieve of TCP Port Used - Check the execution of the script"
    write-host -f Yellow $Message
    $PropertyBag.AddValue("State","WARNING")
    $PropertyBag.AddValue("Message",$Message)
    $PropertyBag.AddValue("ReplicaName",$AvailabilityReplicaName)
    $PropertyBag
    Exit 1
    }





# Scom Object and Property Bag
$api = New-Object -comObject “MOM.ScriptAPI” 
$PropertyBag = $api.CreatePropertyBag()


# Function  GetAODBRepStatus
Function GetAOAvailGroupStatus
                        {
                        param(
                                    [string]$TargetComputer=$DBServer,
                                    $global:Source = "$DBServer\$ServerInstance",
                                    [string]$sqlCommand =
                                            $("
                                            SELECT
                                           
                                            AO_AG.name as AvailGroupName
                                            ,HADR_AO_AGS.synchronization_health as AvailGroup_SyncHealth
                                            ,HADR_AO_AGS.synchronization_health_desc as AvailGroup_SyncHealth_Desc
                                            --,SYSDB.name as DBName
                                           
                                            --,AO_AVREP.replica_server_name
                                            --,HADR_AVAIL_REP_STATE.synchronization_health as AvailReplica_SyncHealth
                                           
                                            --,HADR_AVAIL_REP_STATE.synchronization_health_desc as AvailReplica_SyncHealth_Desc

                                            --,HADR_DB_REP_STATE.synchronization_state as DBReplica_SyncState
                                            --,HADR_DB_REP_STATE.synchronization_state_desc as DBReplica_SyncState_Desc
                                            --,HADR_DB_REP_STATE.synchronization_health_desc as DBReplica_SyncHealth_Desc

                                           
                                            FROM
                                            [sys].[availability_databases_cluster] AO_DB_CLUS
                                            --INNER JOIN sys.databases SYSDB on CAST(SYSDB.group_database_id AS VARCHAR(50)) =  CAST(AO_DB_CLUS.group_database_id AS VARCHAR(50))
                                            INNER JOIN sys.dm_hadr_database_replica_states HADR_DB_REP_STATE on CAST(HADR_DB_REP_STATE.group_database_id AS VARCHAR(50)) = CAST(AO_DB_CLUS.group_database_id AS VARCHAR(50))
                                            --INNER JOIN sys.dm_hadr_availability_replica_states HADR_AVAIL_REP_STATE on HADR_AVAIL_REP_STATE.group_id = HADR_DB_REP_STATE.group_id
                                            INNER JOIN sys.availability_groups AO_AG on AO_AG.group_id = HADR_DB_REP_STATE.group_id
                                            INNER JOIN sys.dm_hadr_availability_group_states HADR_AO_AGS on HADR_AO_AGS.group_id = AO_AG.group_id
                                            --INNER JOIN [sys].[availability_replicas] AO_AVREP on AO_AVREP.replica_id = HADR_DB_REP_STATE.replica_id
                                            WHERE AO_AG.name = '
$AvailGroupName
'
                                           
                                            GROUP BY
                                            AO_AG.name
                                            ,HADR_AO_AGS.synchronization_health
                                            ,HADR_AO_AGS.synchronization_health_desc
                                            "
                                            )
                               )

                               Try
                               {

                                                                                           
                                               

                                            $global:connectionString = "Data Source=$Source,$TcpPort;" +
                                            "Integrated Security=SSPI; " +
                                            "Initial Catalog=master"

                                           
                                            $connection = new-object system.data.SqlClient.SQLConnection($connectionString)
                                           
                               
                                                               
                                            $connection.Open()
                                                                                 
                                                                              


                                            $command = new-object system.data.sqlclient.sqlcommand($sqlCommand,$connection)

                                }
                                catch
                                {
                                write-host -F Red $("Error during sql connection - check the credentials used").ToUpper()
                                #exit 1
                                }

                                $adapter = New-Object System.Data.sqlclient.sqlDataAdapter $command
                                $set = New-Object System.Data.DataSet
                                $adapter.Fill($Set) | Out-Null

                                $connection.Close()
                                $Set.Tables

                               

                              }




# Execute function and get data
try
{
[array]$AvailGroup = GetAOAvailGroupStatus
}
catch
{
$Message = "WARNING - Error during Connection to master database or execution of sql query"
write-host -F Yellow $Message
$PropertyBag.AddValue("State","WARNING")
$PropertyBag.AddValue("Message",$Message)
$PropertyBag.AddValue("DBServer",$DBServer)
$PropertyBag.AddValue("connectstring",$connectionString)
$PropertyBag.AddValue("AvailGroupName",$AvailGroupName)
$PropertyBag.AddValue("AvailGroupStatus","no_data")
$PropertyBag
exit 1
}



if (!($AvailGroup))
{
$Message = "WARNING - Error - No Availability Group have been found"
write-host -F Yellow $Message
$PropertyBag.AddValue("State","WARNING")
$PropertyBag.AddValue("Message",$Message)
$PropertyBag.AddValue("DBServer",$DBServer)
$PropertyBag.AddValue("AvailGroupName","no_data")
$PropertyBag.AddValue("AvailGroupStatus","no_data")
$PropertyBag
exit 1
}



 

try
{
$AvailGroupState = $AvailGroup  | select AvailGroup_SyncHealth_Desc -ExpandProperty AvailGroup_SyncHealth_Desc
}
catch
{
$Message = "Error during Retrieve of Availability Group State"
Write-Host -ForegroundColor Yellow $Message
$PropertyBag.AddValue("State","WARNING")
$PropertyBag.AddValue("Message",$Message)
$PropertyBag.AddValue("DBServer",$DBServer)
$PropertyBag.AddValue("AvailGroupName",$AvailGroupName)
$PropertyBag.AddValue("AvailGroupStatus","no_data")
$PropertyBag
exit 1
}




"AVAILABILITY GROUP: $AvailGroupName"



If ($AvailGroupState -eq "Healthy")

        {
        $Message = "OK - Status of $AvailGroupName Availability Group is Healthy"
        write-host -f Green $Message
        $PropertyBag.AddValue("State","OK")
        $PropertyBag.AddValue("Message",$Message)
        $PropertyBag.AddValue("DBServer",$DBServer)
        $PropertyBag.AddValue("AvailGroupName",$AvailGroupName)
        $PropertyBag.AddValue("AvailGroupStatus","Healthy")
        $PropertyBag
        Exit 0
        }
       


ElseIf ($AvailGroupState -eq "Error")

   
        {
        $Message = "CRITICAL - Status of $AvailGroupName Availability Group is Error"
        write-host -f Red $Message
        $PropertyBag.AddValue("State","CRITICAL")
        $PropertyBag.AddValue("Message",$Message)
      $PropertyBag.AddValue("DBServer",$DBServer)
        $PropertyBag.AddValue("AvailGroupName",$AvailGroupName)
        $PropertyBag.AddValue("AvailGroupStatus","Error")
        $PropertyBag
        Exit 0
        }
       
Else   {
        $Message = "WARNING - Status of $AvailGroupName Availability Group cannot be determined"
        write-host -f yellow $Message
        $PropertyBag.AddValue("State","WARNING")
        $PropertyBag.AddValue("Message",$Message)
      $PropertyBag.AddValue("DBServer",$DBServer)
        $PropertyBag.AddValue("AvailGroupName",$AvailGroupName)
        $PropertyBag.AddValue("AvailGroupStatus","no_data")
        $PropertyBag
        Exit 1
        }
 
 
########################################################################################